feat: add auth req and rectify redirect url
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import { Router, Request, Response } from "express";
|
import { Router, Request, Response } from "express";
|
||||||
import Airtable from "airtable";
|
|
||||||
|
|
||||||
import User from "../models/User";
|
import User from "../models/User";
|
||||||
import Robot from "../models/Robot";
|
import Robot from "../models/Robot";
|
||||||
@@ -9,7 +8,7 @@ import { requireSignIn } from "../middlewares/auth";
|
|||||||
import { genAPIKey } from "../utils/api";
|
import { genAPIKey } from "../utils/api";
|
||||||
import { google } from "googleapis";
|
import { google } from "googleapis";
|
||||||
import { capture } from "../utils/analytics";
|
import { capture } from "../utils/analytics";
|
||||||
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
|
||||||
declare module "express-session" {
|
declare module "express-session" {
|
||||||
@@ -584,13 +583,10 @@ router.post(
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import crypto from 'crypto';
|
|
||||||
|
|
||||||
// Airtable OAuth Routes
|
// Airtable OAuth Routes
|
||||||
router.get("/airtable", (req, res) => {
|
router.get("/airtable", (req: Request, res) => {
|
||||||
const { robotId } = req.query;
|
const authenticatedReq = req as AuthenticatedRequest;
|
||||||
|
const { robotId } = authenticatedReq.query;
|
||||||
if (!robotId) {
|
if (!robotId) {
|
||||||
return res.status(400).json({ message: "Robot ID is required" });
|
return res.status(400).json({ message: "Robot ID is required" });
|
||||||
}
|
}
|
||||||
@@ -602,13 +598,12 @@ router.get("/airtable", (req, res) => {
|
|||||||
.digest('base64url');
|
.digest('base64url');
|
||||||
|
|
||||||
// Store in session
|
// Store in session
|
||||||
req.session.code_verifier = code_verifier;
|
authenticatedReq.session.code_verifier = code_verifier;
|
||||||
req.session.robotId = robotId.toString();
|
authenticatedReq.session.robotId = robotId.toString();
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
client_id: process.env.AIRTABLE_CLIENT_ID!,
|
client_id: process.env.AIRTABLE_CLIENT_ID!,
|
||||||
redirect_uri: process.env.AIRTABLE_REDIRECT_URI!,
|
redirect_uri: process.env.AIRTABLE_REDIRECT_URI!,
|
||||||
|
|
||||||
response_type: 'code',
|
response_type: 'code',
|
||||||
state: robotId.toString(),
|
state: robotId.toString(),
|
||||||
scope: 'data.records:read data.records:write schema.bases:read schema.bases:write',
|
scope: 'data.records:read data.records:write schema.bases:read schema.bases:write',
|
||||||
@@ -619,13 +614,16 @@ router.get("/airtable", (req, res) => {
|
|||||||
res.redirect(`https://airtable.com/oauth2/v1/authorize?${params}`);
|
res.redirect(`https://airtable.com/oauth2/v1/authorize?${params}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get("/airtable/callback", async (req, res) => {
|
router.get("/airtable/callback", async (req: Request, res) => {
|
||||||
|
const authenticatedReq = req as AuthenticatedRequest;
|
||||||
|
const baseUrl = process.env.PUBLIC_URL || "http://localhost:5173";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { code, state, error } = req.query;
|
const { code, state, error } = authenticatedReq.query;
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${process.env.PUBLIC_URL}/robots/${state}/integrate?error=${encodeURIComponent(error.toString())}`
|
`${baseUrl}/robots/${state}/integrate?error=${encodeURIComponent(error.toString())}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,7 +632,7 @@ router.get("/airtable/callback", async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify session data
|
// Verify session data
|
||||||
if (!req.session?.code_verifier || req.session.robotId !== state.toString()) {
|
if (!authenticatedReq.session?.code_verifier || authenticatedReq.session.robotId !== state.toString()) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
message: "Session expired - please restart the OAuth flow"
|
message: "Session expired - please restart the OAuth flow"
|
||||||
});
|
});
|
||||||
@@ -649,10 +647,9 @@ router.get("/airtable/callback", async (req, res) => {
|
|||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
code: code.toString(),
|
code: code.toString(),
|
||||||
client_id: process.env.AIRTABLE_CLIENT_ID!,
|
client_id: process.env.AIRTABLE_CLIENT_ID!,
|
||||||
|
|
||||||
redirect_uri: process.env.AIRTABLE_REDIRECT_URI!,
|
redirect_uri: process.env.AIRTABLE_REDIRECT_URI!,
|
||||||
code_verifier: req.session.code_verifier
|
code_verifier: authenticatedReq.session.code_verifier
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -660,7 +657,7 @@ router.get("/airtable/callback", async (req, res) => {
|
|||||||
const errorData = await tokenResponse.json();
|
const errorData = await tokenResponse.json();
|
||||||
console.error('Token exchange failed:', errorData);
|
console.error('Token exchange failed:', errorData);
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${process.env.PUBLIC_URL}/robots/${state}/integrate?error=${encodeURIComponent(errorData.error_description || 'Authentication failed')}`
|
`${baseUrl}/robots/${state}/integrate?error=${encodeURIComponent(errorData.error_description || 'Authentication failed')}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,40 +675,43 @@ router.get("/airtable/callback", async (req, res) => {
|
|||||||
await robot.update({
|
await robot.update({
|
||||||
airtable_access_token: tokens.access_token,
|
airtable_access_token: tokens.access_token,
|
||||||
airtable_refresh_token: tokens.refresh_token,
|
airtable_refresh_token: tokens.refresh_token,
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear session data
|
|
||||||
req.session.destroy((err) => {
|
|
||||||
if (err) console.error('Session cleanup error:', err);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
res.cookie("airtable_auth_status", "success", {
|
res.cookie("airtable_auth_status", "success", {
|
||||||
httpOnly: false,
|
httpOnly: false,
|
||||||
maxAge: 60000,
|
maxAge: 60000,
|
||||||
}); // 1-minute expiration
|
}); // 1-minute expiration
|
||||||
res.cookie("airtable_auth_message", "Robot successfully authenticated", {
|
// res.cookie("airtable_auth_message", "Robot successfully authenticated", {
|
||||||
|
// httpOnly: false,
|
||||||
|
// maxAge: 60000,
|
||||||
|
// });
|
||||||
|
|
||||||
|
res.cookie('robot_auth_robotId', req.session.robotId, {
|
||||||
httpOnly: false,
|
httpOnly: false,
|
||||||
maxAge: 60000,
|
maxAge: 60000,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.redirect(
|
// Clear session data
|
||||||
`${process.env.PUBLIC_URL}/robots/${state}/integrate || http://localhost:5173/robots/${state}/integrate`
|
authenticatedReq.session.destroy((err) => {
|
||||||
);
|
if (err) console.error('Session cleanup error:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
const redirectUrl = `${baseUrl}/robots/`;
|
||||||
|
|
||||||
|
res.redirect(redirectUrl);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Airtable callback error:', error);
|
console.error('Airtable callback error:', error);
|
||||||
res.redirect(
|
res.redirect(
|
||||||
`${process.env.PUBLIC_URL}/robots/${req.session.robotId}/integrate?error=${encodeURIComponent(error.message)}`
|
`${baseUrl}/robots/${req.session.robotId}/integrate?error=${encodeURIComponent(error.message)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get Airtable bases
|
// Get Airtable bases
|
||||||
router.get("/airtable/bases", async (req: AuthenticatedRequest, res) => {
|
router.get("/airtable/bases", async (req: Request, res) => {
|
||||||
|
const authenticatedReq = req as AuthenticatedRequest;
|
||||||
try {
|
try {
|
||||||
const { robotId } = req.query;
|
const { robotId } = authenticatedReq.query;
|
||||||
if (!robotId) {
|
if (!robotId) {
|
||||||
return res.status(400).json({ message: "Robot ID is required" });
|
return res.status(400).json({ message: "Robot ID is required" });
|
||||||
}
|
}
|
||||||
@@ -748,7 +748,8 @@ router.get("/airtable/bases", async (req: AuthenticatedRequest, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update robot with selected base
|
// Update robot with selected base
|
||||||
router.post("/airtable/update", async (req: AuthenticatedRequest, res) => {
|
router.post("/airtable/update", async (req: Request, res) => {
|
||||||
|
const authenticatedReq = req as AuthenticatedRequest;
|
||||||
const { baseId, robotId , tableName,tableId} = req.body;
|
const { baseId, robotId , tableName,tableId} = req.body;
|
||||||
|
|
||||||
if (!baseId || !robotId) {
|
if (!baseId || !robotId) {
|
||||||
@@ -772,7 +773,7 @@ router.post("/airtable/update", async (req: AuthenticatedRequest, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
capture("maxun-oss-airtable-integration-created", {
|
capture("maxun-oss-airtable-integration-created", {
|
||||||
user_id: req.user?.id,
|
user_id: authenticatedReq.user?.id,
|
||||||
robot_id: robotId,
|
robot_id: robotId,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
@@ -785,8 +786,9 @@ router.post("/airtable/update", async (req: AuthenticatedRequest, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Remove Airtable integration
|
// Remove Airtable integration
|
||||||
router.post("/airtable/remove", requireSignIn, async (req: AuthenticatedRequest, res) => {
|
router.post("/airtable/remove", requireSignIn, async (req: Request, res) => {
|
||||||
const { robotId } = req.body;
|
const authenticatedReq = req as AuthenticatedRequest;
|
||||||
|
const { robotId } = authenticatedReq.body;
|
||||||
if (!robotId) {
|
if (!robotId) {
|
||||||
return res.status(400).json({ message: "Robot ID is required" });
|
return res.status(400).json({ message: "Robot ID is required" });
|
||||||
}
|
}
|
||||||
@@ -810,7 +812,7 @@ router.post("/airtable/remove", requireSignIn, async (req: AuthenticatedRequest,
|
|||||||
});
|
});
|
||||||
|
|
||||||
capture("maxun-oss-airtable-integration-removed", {
|
capture("maxun-oss-airtable-integration-removed", {
|
||||||
user_id: req.user?.id,
|
user_id: authenticatedReq.user?.id,
|
||||||
robot_id: robotId,
|
robot_id: robotId,
|
||||||
deleted_at: new Date().toISOString(),
|
deleted_at: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
@@ -825,9 +827,10 @@ router.post("/airtable/remove", requireSignIn, async (req: AuthenticatedRequest,
|
|||||||
|
|
||||||
|
|
||||||
// Fetch tables from an Airtable base
|
// Fetch tables from an Airtable base
|
||||||
router.get("/airtable/tables", async (req: AuthenticatedRequest, res) => {
|
router.get("/airtable/tables", async (req: Request, res) => {
|
||||||
|
const authenticatedReq = req as AuthenticatedRequest;
|
||||||
try {
|
try {
|
||||||
const { baseId, robotId } = req.query;
|
const { baseId, robotId } = authenticatedReq.query;
|
||||||
|
|
||||||
if (!baseId || !robotId) {
|
if (!baseId || !robotId) {
|
||||||
return res.status(400).json({ message: "Base ID and Robot ID are required" });
|
return res.status(400).json({ message: "Base ID and Robot ID are required" });
|
||||||
|
|||||||
Reference in New Issue
Block a user