Giter Site home page Giter Site logo

Comments (16)

jamauro avatar jamauro commented on May 18, 2024 7

I have a POC. What do y'all think of this?

// in 'meteor/fetch'
function isExternalUrl(url) {
  return url.includes('://');
}

const originalFetch = global.fetch;
global.fetch = function(url, options = {}) {
  if (!isExternalUrl(url) && Meteor.userId() && (!options.headers || !options.headers.get('authorization'))) {
    options.headers = options.headers || new global.Headers();
    options.headers.append('Authorization', `Bearer ${Accounts._storedLoginToken()}`);
  }

  return originalFetch(url, options);
};

exports.fetch = global.fetch;
exports.Headers = global.Headers;
exports.Request = global.Request;
exports.Response = global.Response;
// in 'meteor/webapp' (in which case we wouldn't import WebApp but I didn't go that far in this POC)
import { WebApp } from 'meteor/webapp';
import { Accounts } from 'meteor/accounts-base';
import { DDP } from 'meteor/ddp';

// Middleware to set up DDP context for Meteor.userId()
const ddpUserContextMiddleware = (req, res, next) => {
  const { authorization } = req.headers;
  const [ prefix, token ] = authorization?.split(' ') || [];

  const user = (prefix === 'Bearer' && token) ? Meteor.users.findOne({
    'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(token),
  }) : undefined;

  const ddpSession = {
    userId: user ? user._id : undefined,
    connection: {},
    isSimulation: false,
  };

  DDP._CurrentInvocation.withValue(ddpSession, next);
};

// Apply middleware to all connectHandlers
WebApp.connectHandlers.use(ddpUserContextMiddleware);
// server
WebApp.connectHandlers.use('/something', (req, res, next) => {
  const userId = Meteor.userId();
  const user = Meteor.user();
  if (userId) {
    console.log('User ID:', userId, user);
    // Now you can use userId as needed
  } else {
    console.log('User ID not found');
  }
  next();
});
// client
import { fetch } from 'meteor/fetch'

fetch('/something')

from meteor.

rj-david avatar rj-david commented on May 18, 2024 1

The normal use case is a 3rd party system accessing a rest endpoint of a meteor app. With this use case, the code above has shortcomings

  1. Meteor fetch will not work
  2. loginToken requires existing login session that is not expired

For the second point, this is normally the case for machine-to-machine integrations which requires long-lived access tokens like in oauth.

from meteor.

nachocodoner avatar nachocodoner commented on May 18, 2024 1

What is the normal use case of a meteor app using rest api instead of methods to access the server?

Maybe another use-case less explored where using HTTP endpoints over the DDP is when using a Server-Side Rendering (SSR) approach for your app, not really extended approach on Meteor though. But if we take ever that direction further, having Meteor context on those endpoints would be great. I like the approach from @jamauro above.

from meteor.

rj-david avatar rj-david commented on May 18, 2024 1

What is the normal use case of a meteor app using rest api instead of methods to access the server?

Maybe another use-case less explored where using HTTP endpoints over the DDP is when using a Server-Side Rendering (SSR) approach for your app, not really extended approach on Meteor though. But if we take ever that direction further, having Meteor context on those endpoints would be great. I like the approach from @jamauro above.

The challenge here is that the starting point in the client does not have that context, yet (client is not yet running). This is solved through the use of cookies by existing packages.

from meteor.

StorytellerCZ avatar StorytellerCZ commented on May 18, 2024

Pull request to change things would only make sense if targeted against Meteor 3.

from meteor.

rj-david avatar rj-david commented on May 18, 2024

Was there a package similar to this wherein interfacing with the rest points require some validation token to "log in" the user?

from meteor.

jamauro avatar jamauro commented on May 18, 2024

I think this would be a nice addition to core. Curious how'd you go about implementing it.

One idea would be to automatically include the loginToken inside Meteor's fetch. And then use it behind the scenes provide access to the userId and user.

I shared one way to currently do it here: https://forums.meteor.com/t/call-meteor-userid-from-connecthandler/61072/2?u=jam

Maybe there's an even better way.

from meteor.

rj-david avatar rj-david commented on May 18, 2024

@jamauro, that will be a security issue attaching the token to meteor fetch as it is used universally (not only for your app's endpoints)

from meteor.

jamauro avatar jamauro commented on May 18, 2024

@rj-david good point. I updated the snippet above to exclude external URLs to avoid that issue.

maybe there's a better solution entirely. curious to hear what that could be.

from meteor.

rj-david avatar rj-david commented on May 18, 2024

What is the normal use case of a meteor app using rest api instead of methods to access the server?

from meteor.

jamauro avatar jamauro commented on May 18, 2024

It's not a bad question. It's kind of an anti-pattern imo to reach for a WebApp endpoint when you can just use a method. Having said that:

  1. It has come up in the forums a number of times.
  2. In the past people have built packages to get the userId inside WebApp.connectHandlers – though you still couldn't simply use Meteor.userId().
  3. It does feel like an artificial limitation. It'd be nice if Meteor.userId() and Meteor.user() "just worked" inside WebApp.connectHandlers. After all, the Meteor Guide says they can be used anywhere but publish functions.

from meteor.

rj-david avatar rj-david commented on May 18, 2024

What is the normal use case of a meteor app using rest api instead of methods to access the server?

One thing that I can think of is accessing user-based files. The file requires authentication for access. But instead of using DDP, use https which is more efficient in this case.

(Of course, one can argue to just use S3 and use a signed url from the meteor app to access the files.)

from meteor.

nesbtesh avatar nesbtesh commented on May 18, 2024

The primary application of our system involves creating Excel spreadsheets and leveraging AWS Lambda for tasks that require significant computational resources. We utilize Meteor for data management, but often find ourselves executing tasks outside of Node.js. This approach is taken because some processes are more efficiently handled outside the Node.js environment, especially when dealing with large volumes of data or computationally intensive tasks. This strategy allows us to optimize performance and manage our workload more effectively.

import bodyParser from "body-parser";
import { Meteor } from "meteor/meteor";
import { WebApp } from "meteor/webapp";
// Assuming this is running on the server

WebApp.connectHandlers.use(bodyParser.json());

// Step 1: Initialize an object to store method names
global.MethodsList = {};

// Step 2: Create a wrapper for Meteor.methods
const originalMeteorMethods = Meteor.methods;

WebApp.connectHandlers.use("/api", async (req, res, next) => {
	// Extract the Authorization header
	const authHeader = req.headers.authorization;

	if (authHeader && authHeader.startsWith("Bearer ")) {
		const token = authHeader.slice(7); // Remove "Bearer " from the start

		const hashedToken = Accounts._hashLoginToken(token);

		// Find the user by the token
		const user = await Meteor.users.findOneAsync({
			"services.resume.loginTokens.hashedToken": hashedToken,
		});

		if (user) {
			// Attach user information to the request object
			req.user = user;
			req.userId = user._id;

			// Proceed to the next handler/middleware
			next();
		} else {
			// Authentication failed
			res.writeHead(401);
			res.writeHead(500, { "Content-Type": "application/json" });
			res.end(
				JSON.stringify({
					status: "error",
					message: "Authentication failed",
				}),
			);
		}
	} else {
		// No or improperly formatted Authorization header present
		next();
	}
});

async function responseHandler(callback, req, res) {
	try {
		const result = await callback(req, res);
		res.writeHead(200, { "Content-Type": "application/json" });
		res.end(JSON.stringify(result));
	} catch (error) {
		res.writeHead(500, { "Content-Type": "application/json" });
		if (error && error.reason) {
			res.end(
				JSON.stringify({
					status: "ko",
					reason: error.reason,
				}),
			);
		} else {
			res.end(JSON.stringify(error));
		}
	}
}

function createApiFunction({ url, func, method }) {
	console.log("createApiFunction", url, func, method);

	// Register API endpoint
	if (method === "GET") {
		WebApp.connectHandlers.use(`/api${url}`, async (req, res) => {
			responseHandler(
				async (req) => {
					const userId = await req.userId;
					return func.apply(this, [req.body, userId]);
				},
				req,
				res,
			);
		});
	} else if (method === "POST") {
		WebApp.connectHandlers.use(`/api${url}`, async (req, res) => {
			responseHandler(
				async (req) => {
					const userId = await req.userId;
					return func.apply(this, [req.body, userId]);
				},
				req,
				res,
			);
		});
	} else if (method === "PUT") {
		WebApp.connectHandlers.use(`/api${url}`, async (req, res) => {
			responseHandler(
				async (req) => {
					const userId = await req.userId;
					return func.apply(this, [req.body, userId]);
				},
				req,
				res,
			);
		});
	}
}

Meteor.methods = function (methods) {
	const methodsToRegister = {};

	Object.keys(methods).forEach((methodName) => {
		// Store method names in the global object
		MethodsList[methodName] = true;

		if (typeof methods[methodName] === "function") {
			// methodsToRegister[methodName] = methods[methodName];
			methodsToRegister[methodName] = function func(...args) {
				this.unblock();
				args[1] = Meteor.userId();
				return methods[methodName].apply(this, args);
			};
		} else {
			methodsToRegister[methodName] = function func(...args) {
				this.unblock();

				args[1] = Meteor.userId();

				return methods[methodName].function.apply(this, args);
			};

			if (methods[methodName].api) {
				createApiFunction({
					...methods[methodName].api,
					func: methods[methodName].function,
				});
			}
		}
	});

	// Call the original Meteor.methods function
	originalMeteorMethods(methodsToRegister);
};

The primary objective is to expose all of our methods to a GPT4 bot as well as standarize the way of creating API like this:

Meteor.methods({
	helloWord: {
		function: (data, userId) => {
			// Meteor.userId();

			console.log("hello user: ", userId);
			// console.log("hello user data: ", Meteor.user());
			console.log("hello", data);
			return "Hello World!";
		},
		api: {
			method: "POST",
			url: "/hello",
		},
                ai: {
                   description: "use to say hello",
                   params: {
                          name: String
                   }
                }
	},
})

from meteor.

rj-david avatar rj-david commented on May 18, 2024

@nesbtesh, how do you handle expired tokens for this case?

from meteor.

nesbtesh avatar nesbtesh commented on May 18, 2024

this is just a test but I will add it... @rj-david how do you suggest we handle it?

from meteor.

rj-david avatar rj-david commented on May 18, 2024

this is just a test but I will add it... @rj-david how do you suggest we handle it?

On top of my head, this should be through a mechanism of long-lived sessions like oauth if the access is coming from a 3rd party system without an actual logged-in user accessing the app.

Although when the access is made on behalf of a logged-in user, that looks like a clear use-case for this.

from meteor.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.