NodeJS: What is the best way to implement OAuth client logic? - javascript

I'm currently implementind OpenID/OAuth authorization in my project and using openid-client package for that.
In this package we initialize an openid client with the following code:
const { Issuer } = require('openid-client');
Issuer.discover('https://accounts.google.com') // => Promise
.then(function (googleIssuer) {
console.log('Discovered issuer %s %O', googleIssuer.issuer, googleIssuer.metadata);
});
const client = new googleIssuer.Client({
client_id: 'zELcpfANLqY7Oqas',
client_secret: 'TQV5U29k1gHibH5bx1layBo0OSAvAbRT3UYW3EWrSYBB5swxjVfWUa1BS8lqzxG/0v9wruMcrGadany3',
redirect_uris: ['http://localhost:3000/cb'],
response_types: ['code'],
// id_token_signed_response_alg (default "RS256")
// token_endpoint_auth_method (default "client_secret_basic")
}); // => Client
How we can implement a singleton client logic?
To create a client only once and reuse it all over the application?
I've tried to create a separate class for that but not sure if it is correct:
import { Issuer } from 'openid-client';
export class OpenIdClient {
async createOpenIdClient() {
const issuer = await Issuer.discover(encodeURI(`http://localhost:3101/.well-knownendpoint`));
const client = await new issuer.Client({
client_id: 'clientId',
client_secret: 'clientSecret'
})
return client;
}
}

You're close here, and using a class can be a solution, though I find classes to be less useful if all you're after is a singleton. Classes are great when you want to instance more than one time (or at least have the ability to).
I usually just use a plain variable or object for a singleton. No need to use anything more complicated here.
Note there are other answers for how to create singletons, so this is just my own opinion on the best/simplest solution for your needs.
In a separate file (e.g. oauth-client.js):
// oauth-client.js
const { Issuer } = require('openid-client');
Issuer.discover('https://accounts.google.com')
.then(function (googleIssuer) {
console.log('Discovered issuer %s %O', googleIssuer.issuer, googleIssuer.metadata);
});
const client = new googleIssuer.Client({
client_id: 'redacted',
client_secret: 'redacted',
redirect_uris: ['http://localhost:3000/cb'],
response_types: ['code'],
});
module.exports = client;
The first time it's loaded by a file, it'll instantiate. Any subsequent times it's loaded in the node.js app, it won't re-instantiate. It'll reuse the entity that's already in memory.
//other file, let's pretend in the same file path.
const oauthClient = require('./oauth-client');
...
If you're wanting to make sure for yourself, put a log line at the top of the singleton file. Then load the file from two different parts of your app. You'll see that it only logs it out once.
To answer the question in the comments below: Swap these lines for an object destructured approach, which makes it nice for if you want to export multiple things from the file:
...
module.exports = { oauthClient: client }
const { oauthClient } = require('./oauth-client');
...
Since we aren't exporting more than one thing (the client), the original answer is still fine.

Related

What's the advantage of fastify-plugin over a normal function call?

This answer to a similar question does a great job at explaining how fastify-plugin works and what it does. After reading the explanation, I still have a question remaining; how is this different from a normal function call instead of using the .register() method?
To clarify with an example, how are the two approaches below different from each other:
const app = fastify();
// Register a fastify-plugin that decorates app
const myPlugin = fp((app: FastifyInstance) => {
app.decorate('example', 10);
});
app.register(myPlugin);
// Just decorate the app directly
const decorateApp = (app: FastifyInstance) => {
app.decorate('example', 10);
};
decorateApp(app);
By writing a decorateApp function you are creating your own "API" to load your application.
That said, the first burden you will face soon is sync or async:
decorateApp is a sync function
decorateAppAsync within an async function
For example, you need to preload something from the database before you can start your application.
const decorateApp = (app) => {
app.register(require('#fastify/mongodb'))
};
const businessLogic = async (app) => {
const data = await app.mongo.db.collection('data').find({}).toArray()
}
decorateApp(app)
businessLogic(app) // whoops: it is async
In this example you need to change a lot of code:
the decorateApp function must be async
the mongodb registration must be awaited
the main code that loads the application must be async
Instead, by using the fastify's approach, you need to update only the plugin that loads the database:
const applicationConfigPlugin = fp(
+ async function (fastify) {
- function (fastify, opts, next) {
- app.register(require('#fastify/mongodb'))
- next()
+ await app.register(require('#fastify/mongodb'))
}
)
PS: note that fastify-plugin example code misses the next callback since it is a sync function.
The next bad pattern will be high hidden coupling between functions.
Every application needs a config. Usually, the fastify instance is decorated with it.
So, you will have something like:
decorateAppWithConfig(app);
decorateAppWithSomethingElse(app);
Now, decorateAppWithSomethingElse will need to know that it is loaded after decorateAppWithConfig.
Instead, by using the fastify-plugin, you can write:
const applicationConfigPlugin = fp(
async function (fastify) {
fastify.decorate('config', 42);
},
{
name: 'my-app-config',
}
)
const applicationBusinessLogic = fp(
async function (fastify) {
// ...
},
{
name: 'my-app-business-logic',
dependencies: ['my-app-config']
}
)
// note that the WRONG order of the plugins
app.register(applicationBusinessLogic);
app.register(applicationConfigPlugin);
Now, you will get a nice error, instead of a Cannot read properties of undefined when the config decorator is missing:
AssertionError [ERR_ASSERTION]: The dependency 'my-app-config' of plugin 'my-app-business-logic' is not registered
So, basically writing a series of functions that use/decorate the fastify instance is doable but it adds
a new convention to your code that will have to manage the loading of the plugins.
This job is already implemented by fastify and the fastify-plugin adds many validation checks to it.
So, by considering the question's example: there is no difference, but using that approach to a bigger application
will lead to a more complex code:
sync/async loading functions
poor error messages
hidden dependencies instead of explicit ones

Mocha.js - How to save a global variable?

I'm working with Mocha.js for testing in a Node.js - Express.js - Firebase
I need a token from Firebase to access the API endpoints, I have a before hook in all my files, but after about 250 tests, probably calling the authentication endpoint multiple times, I'm getting rate limited by firebase.
I want to get the token once and use it in all my tests.
The tests are spread in different files, I have an index.js that requires them all.
I'm aware of Root Level Hooks, but how can I save the token and use it in all my separate files?
Thanks!
you can create a function that gets the token. then call it. then create your test suite only after that
function getToken(callback) {
//
}
// define tests
function allTests(token) {
describe(xxxxxx, function () {
it(xxxxxxxxx, function() {
//
})
});
}
// start all
getToken(function(token) {
allTests(token);
});
I managed to solve it myself, if anyone needs an answer on how to approach it, take a look at this.
I have multiple files where we write our unit testing, we unite them in an index.spec.js that we execute for testing ($ mocha index.spec.js)
I created a utility file that looks like this:
let token;
(() => { token = getYourToken() })()
module.exports = {
getToken: () => {
return new Promise((resolve) => {
const interval = setInterval(() => {
if (token) {
clearInterval(interval);
resolve(token);
}
}, 100);
});
}
};
Basically, it's a singleton, in the index.spec.js I require this file, executing the 'getYourToken()' once (add your logic to get token here). Then I store it in a variable that then I export.
In the export, I use an interval because my current code is not using promises, use your best practice method, interval + Promise worked for me.
This way I require this file in my tests and get the token I got at the beginning once, avoiding rate-limiting and any issue with firebase.
Create a JSON file in your test root directory.
Import the file.
Append a token property with the token value.
Then import it anywhere to access the token property .

typescript, how to reference the unexported OAuth2Client? google-api-nodejs-client

I am trying to follow https://developers.google.com/sheets/api/quickstart/nodejs tutorial
I am using typescript and I like the type annotation and auto-complition features it provides and I would like to re-write the example in typescript and async rather than callbacks.
Sadly I am stuck, OAuth2Client.
in my code I creating an oauth client inside a function like this:
async function setupClient() {
const content: string = await fs.readFile("credentials.json", "utf8");
const credentials = JSON.parse(content);
// eslint-disable-next-line camelcase
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client: o2 = new google.auth.OAuth2(
client_id,
client_secret,
redirect_uris[0]
);
type OAuth2Client = typeof oAuth2Client;
return oAuth2Client;
}
And I would like to delegate that client to two other setup functions:
function setupAuthUrl(oAuth2Client, scopes) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: "offline",
scope: scopes,
});
return authUrl;
}
function setupToken(oAuth2Client, code) {
//code
}
But I am struggling to annotate oauth2client type as it is not directly exposed by anything?
One would guess that it is exposed under the 'oauth2_v2' namespace? but doesn't seem to be it.
I have been looking for a way to reference this type prior to instantiation, preferably via importing, sadly, when installing this libary via npm install googleapis it doesn't provide a dependency to niether of these google-auth-library, googleapis-common where it is exported and exposed for importing. eslint errors unless it adds the dependency and I struggle to see why It is required as the type should be accessable for code completion with ease.
As for type aliasing:
type OAuth2Client = typeof GoogleApis.prototype.auth.OAuth2.prototype;
This is the only option I could figure out that doesn't throw an error/warning(object as namespace) for aliasing. this is both a long line and very weird one at that too coming from other OO languages
TLDR: What's the prefered way to access OAuth2Client and maybe other types for code completion in typescript?
As of Jun 19 a merge request adding this feature has been approved.
https://github.com/googleapis/google-api-nodejs-client/issues/2208
import { google, Auth } from 'googleapis';
const oauthClient: Auth.OAuth2Client = new google.auth.OAuth2();
Thanks for angelxmoreno for the example

Using module within module in NodeJS

I'm fairly new to nodejs. Writing my first application. I'm pretty used to php.
In order to keep code organized and clean, i always write functions in separate files and include them as required in php.
However, in nodejs i've had to require them like i would require a module.
For example.
functions.js
module.exports = {
check_db : function(key){
},
check_cache : function(key){
memcached.get(key,function(err, data){
console.log(data);
});
},
};
Included that in the main app like so
// Establish connection with cache and database
const mysql = require('mysql2');
const Memcached = require('memcached');
const memcached = new Memcached('localhost:11211');
const bb = require('bot-brother');
//Load the database cache functions
const dbc = require("./functions");
dbc.check_cache(123);
Now i can access the functions from dbc from the main app file, but i cannot use modules that have been required in the main app from the functions file.
I get an error that memcached is not defined.
How can i go about solving this?
Simple solution, you can require("memcached") in the functions.js file and create the server here. But I wouldn't go with this solution, as, if you need memcache somewhere else, you would have opened many connections on the memcache server.
Another, and cleaner solution IMO, is to inject the memcache dependency into your services (or functions as you call them). (this practice is called dependency injection if you want to learn about it and what are the benefits of it)
Here is how it would work:
you still create the memcache connection in the main file ;
instead of exporting a raw json object in your functions.js, you export a function that takes an argument (here memcache)
in your main file, you require that function and call it to get the service you want.
Here is what the code would look like:
main.js
//Load the database cache functions
const dbcFactory = require("./functions");
const dbc = dbcFactory(memcached)
functions.js
module.exports = function (memcached) {
return {
check_db : function(key){},
check_cache : function(key){
memcached.get(key,function(err, data){
console.log(data);
})
}
};

How to organize models in a node.js website?

I have just started experimenting with building a website using node.js, and I am encountering an issue when organizing the models of my project.
All the real world examples I have found on the Internet are using Mongoose. This library allows you to define your models in a static way. So you can write this:
// models/foo.js
module.exports = require('mongoose').model('Foo', ...);
// app.js
mongoose.connect(...);
// some_controller_1.js
var Foo = require('./models/foo');
Foo.find(...);
// some_controller_2.js
var Foo = require('./models/foo');
Foo.find(...);
But since I don't want to use MongoDB, I need another ORM. And all the other ORMs I have found don't allow this. You first need to create an instance, and then only you can register your models. Also they don't seem to allow access to the list of registered models.
So I tried doing this:
// models/user.js
var registrations = [];
module.exports = function(sequelize) {
var result = null;
registrations.forEach(function(elem) {
if (elem.db == sequelize)
result = elem.value;
});
if (result) return result;
// data definition
var user = sequelize.define("User", ...);
registrations.push({ db: sequelize, value: user });
return user;
};
Which I can use this like:
// some_controller_1.js
var Foo = require('./models/foo')(app.get('database'));
Foo.find(...); // using Foo
But these small header and footer that I have to write on every single model file are a bit annoying and directly violate the "don't repeat youself" principle. Also, while not a huge issue, this is kind of a memory leak since the "sequelize" object will never be freed.
Is there a better way to do, which I didn't think about?
You can find an article about handling models with sequelize here: http://sequelizejs.com/articles/express#the-application
You basically just create a models/index.js as described here: http://sequelizejs.com/articles/express#block-3-line-0
Afterwards you just put your model definitions within files in the models folder as pointed out here: http://sequelizejs.com/articles/express#block-4-line-0

Categories

Resources