Comments (16)
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.
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
- Meteor fetch will not work
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.
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.
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.
Pull request to change things would only make sense if targeted against Meteor 3.
from meteor.
Was there a package similar to this wherein interfacing with the rest points require some validation token to "log in" the user?
from meteor.
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.
@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.
@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.
What is the normal use case of a meteor app using rest api instead of methods to access the server?
from meteor.
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:
- It has come up in the forums a number of times.
- In the past people have built packages to get the userId inside
WebApp.connectHandlers
β though you still couldn't simply useMeteor.userId()
. - It does feel like an artificial limitation. It'd be nice if
Meteor.userId()
andMeteor.user()
"just worked" insideWebApp.connectHandlers
. After all, the Meteor Guide says they can be used anywhere but publish functions.
from meteor.
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.
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.
@nesbtesh, how do you handle expired tokens for this case?
from meteor.
this is just a test but I will add it... @rj-david how do you suggest we handle it?
from meteor.
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)
- [3.0] insertAsync on client won't persist local document HOT 2
- Underscore _.where function return only one item HOT 2
- 3.0-beta.6 > Create VueJs App > Cannot find module 'fibers' HOT 5
- Hot Reload with false-positive on Meteor 3.0 using module-resolver Babel plugin HOT 2
- Server HMR running with frontend changes
- [2.15] libcrypto.so.3 not found on CircleCI HOT 4
- Should Meteor.loginWithPassword have a promised-based alternative? HOT 3
- Meteor.loginWithPassword should have a similar signature as Accounts.createUser HOT 5
- Meteor 3: TypeError: Cannot read properties of undefined (reading βformatβ) HOT 2
- Missing Accounts.addEmailAsync
- Meteor 3.0-beta.7 not working HOT 2
- CoffeeScript/Babel turning classes into functions on Cordova/Desktop HOT 1
- Problem importing packages on meteor 3.0-beta.7 HOT 16
- Google Maps JS API will stop working in Cordova in May for iOS+Android HOT 3
- [ Meteor v3 ] - Error in OAuth Server: findOne
- [v3.0-beta.7] Cannot create project or update meteor to v3 error: Command failed: HOT 2
- [Meteor 3] WebApp.addRuntimeConfigHook is broken HOT 1
- [3.0] error on boot.js TypeError: next.runImage.call is not a function HOT 7
- [3.0-rc.0] error on boot.js TypeError: next.runImage.call is not a function HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from meteor.