Self host API

yup_disc
by yup_disc · 5 posts
1 year ago in Javascript
Posted 1 year ago · Author
Dear mafias,

Since I have created 4 major APIS for dealing with IMVU's internal APIs. I thought why not let you host your own. These APIs I made have become the central of my projects and of other developers and have made it sincerely easy dealing with AUTH for IMVU and refreshing it too.

This API is very easy to expand as everything is typed. You can guess it's made in Typescript. The database and httpclient are decorated globally in every file. So no need for seperate imports. On every request: FastifyRequest u have the choice of those 2. Making it very easy for development. I passed data on from middlewares through each route, so data passing is easy and no info has to be requested 2 times. I also create helper functions and distuingished with what should be a helper or middleware.

It uses a Prisma ORM, which has a fully type safe database interface. U can also choose to import the seperate interfaces from the generated client that comes within this API. Confused on what to insert? No worreis VS Code intellisense will give u a wide variety of options. Prisma is a very good ORM and provides a wealthy range of options to choose from. It's totally up to you.

What do I need?

A working sql database. Since, I use ORM and the models are defined inside the project it doesn't really matter if u use Postgresql, MYSQL. I kept that in mind so u'r free to whatever u want. Does that mean I can use NOSQL (MongoDB and other nosql databases) No. Only SQL DATABASES, don't even try to use NOSQL it will fail. And please don't use SQLITE (I'm not sure what kind of issues this can cause), check on Prisma's docs what u can use.
NODE JS: Minimum 19 I think. Haven't really expiremented. But, that's the sweet spot for me.

A working table


How to use?


    1: Download the attachment, u can put it in a virus scanner etc... Downloaded? Great, extract it anywhere u'd like. Delete package_lock.json, not package.json that one is essential.
    Run NPM install. This will install the essential u need to run the API. Check for Nodemon, to hot reload the api on file saving etc...
    3: Create an .env and place this value in it: DATABASE_URL="mysql://username:password_user@localhost:3306/database_name"
    Your job is to replace these values so it works for you.
    Done? Great. Now, run
    Code
    npx prisma db push
    . This will migrate the models I defined into the database, u will need these to make sure the database works. This also generates an api client, for which is needed to make my plugin work. But, after u pushed the DB prisma will generate and install it, so it's all automatic don't worry.
    4: Run it: Now for development:
    Code
    nodemon main.ts
    . For production? I'd suggest u'd look into PM2. https://pm2.io/

IMPORTANT
This is a fresh API, it comes with a built-in auth system (no 2fa support. Reason: Too extensive u can implement it yourself as I did on my own apis). Now, the only thing for u to do is expand the API this way it picks the session according to your requirements, I let you (the developer) think of a way to integrate this system so it suits your needs, the adding and renewing authentication has been done for you.

This is a boiler plate for session managing API, minus the session picking (that is up to you to integrate), we all know how annoying it is, to not have a place to easily restore our sessions or to remake an entire auth system everytime we start up a new project.

TIPS:
As I mentioned earlier, integrating a session managing system is required if u want to easily take advantage of the API IMVU routes back to your own self hosted API. Now, I installed something called requestContext that is decorated within the Request parameter of each function eg: Prehandlers, handlers and other types of hooks on fastify.
Session manager
    1: Create a middleware function, that gets the session, (recursively is best) this way the API will keep selecting until it has either run out or has the session it needs. How u fetch those sessions from imvu (https://api.imvu.com/loginyup_disc, put in headers: cookie: session.cookie for example) is up to you. U can either do one by one or build up a list of promises and get the first success and abort the others. Whatever fits your needs
    2: Any session that fails, update it with a valid false in the database so the API won't redo these sessions which were rendered invalid on before going requests. Preferably, perform one query with prisma, u can also do it in between wouldn't really make a lot of difference.
    3: Got it? Great. Now you see that request parameter defined in the function of this middleware? Okay, that one has a parameter eg: request.requestContext. It's a shared store in which u can pass data on from one handler to another. Only those which live in the same state so middlewares (before) > handler > middlewares (after). Use request.requestContext.set("session", session_variable) and u'r set. Now make sure to fastify.addHook("preHandler", middleware_function_session) and in every route in the instance u registered u can safely do: request.requestContext.get("session") and to sugar coat it in typescript: request.requestContext.get("session") as sessions;

How to make requests to IMVU then?

As I stated I also decorated this API with a singleton AXIOS http client. Which has it's base URL set to API.IMVU.COM, this means u can just type the path of each endpoints to your needs, and not having to retype api.imvu.com in every method u call.

It's actually fairly easier. In every authenticated request u need to have the user selected session. How this system works is up to you, as long as u have the cookies and sauce entry we're good.

For stateless requests aka: GET, HEAD, TRACE. Anything that does not modify a resource on the API does not need a sauce.
For stateful requests aka: POST, PUT, PATCH, DELETE: Requires the header: x-imvu-sauce with the sauce value. The API's auth system provides u with this upon authentication success, take a look in PHPmyadmin or anything and look in sessions table.

EG if u have a session u can do for example:
Here we have an example function of how I call my stuff. Now u won't find this in the API, but it's to give u an IDEA how can u make it so that it works easy and fast.
Code
export default async function deleteSession(
  request: FastifyRequest,
  reply: FastifyReply
) {
import { sessions, users } from "@prisma/client";
import { isAxiosError } from "axios";
import { FastifyRequest, FastifyReply } from "fastify";

export default async function deleteSession(
  request: FastifyRequest,
  reply: FastifyReply
) {
  const user = request.requestContext.get("user") as users;
  const session = request.requestContext.get("session") as sessions;
  try {
    await request.httpClient.delete(`/loginyup_disc`, {
      headers: {
        cookie: session.cookie,
        "x-imvu-sauce": session.sauce,
      },
    });
    await request.database.sessions.update({
      where: {
        id: session.id,
      },
      data: {
        valid: false,
      },
    });
    return reply.send({
      status: "Session revoked",
      message: `Session revoked for user: ${user.username}`,
      session: session,
    });
  } catch (e) {
    if (isAxiosError(e)) {
      await request.database.sessions.update({
        where: {
          id: session.id,
        },
        data: {
          valid: false,
        },
      });
      return reply.status(e.response?.status || 500).send({
        status: e.response?.status,
        statusText: e.response?.statusText,
        data: e.response?.data,
        message:
          "Session revoked, but not from IMVU, might have already been revoked or does not exist.",
        session: session,
      });
    }
    return reply.internalServerError("Could not revoke session due to error");
  }
}



Singleton?
Yes, see singleton as the only living instance on a request. Now, everytime we wanna do a request, it's better to have a single instance of something alive, same with the database ORM. This is for several reasons.
    1: U save time. It costs a small amount of time to initiate a httpclient approx 600 to 800 ms if u wanna do a request to IMVU, now imagine this everytime we wanna do a request. If we didn't use a singleton it would keep making copies over and over, needing to take that time as I mentioned earlier and, u can probably imagine that it would take very long if u had an extensive route, doing like 5 or 6 requests, which is common in apis I've seen it come across mine in my V1 and V2 and let me tell you it's not pretty the time it took. Now with a singleton it instantiates once, and takes all the worries away.
    2: Saves memory, yes that's right. Singleton is a one time copy, and the rest that uses is a reference to that object, so it saves memory. Makes sense right? Yes, I think so too.

Any other stuff I need to know?
As a matter of fact yes. The tiny plugins I developed to sugarcoat the parameter on this API are short lived. This means for PRISMA, the connection pool after the request is done and the server calls the close hook, it disconnects. So, your database won't give any errors regarding too many connections, and prisma comes with a nice connection pool making stuff even better.

As for the HTTPClient, a singleton is destroyed on each closing automatically by garbage collector so need to do anything there as far as I know.

The APIS I created for myself have been a blessing and especially with the amount of intellisense u can build into it and hosting your own sure makes any future projects easier. The only challenge for u'd be adding a session manager.

I also included fastify sensible, so u don't have todo:
Code
fastify.code(500).send({})
u can do instead:
Code
fastify.internalServerError(msg? (nullable)
Do note that the message has to be a string and not an object, so sensible will not serialize the data for you. You can json.stringify but it won't look as how it would using the send({}) method. It would just print out a single ugly string, so keep that in mind.

This API uses a basic auth system. Meaning u have to use Basic username and password base64 and pass it on in the Authorization header.

Every user is bound to a api key.

Current features
    1: Authentication instance: Adding users to the API and renewing sessions of those which have been added.
    2: API KEY admin system. U can add and remove keys for now. Look at routes and code for further information.


If I missed anything. Let me know. I hope u find this a teachable moment and choose to expand this API as one of your own. It makes the job a lot easier. And adding endpoints for your own needs is easy too.
Posted 1 year ago · Author
@terryracer


The purpose is to make it easier for developers. Imagine u have made 5 bots for IMVU with each their own purpose it doesn't matter what and each requires to perform authenticated requests. U need sessions which are key ingredient to doing so. Aka OSCSID cookie and imvu sauce which acts as a csrf token in order to do this u need either an auth system or hard code the cookies. But, nobody wanna go for option b unless it's some quick, u wanna automate. Instead of rebuilding or integrating the same auth system over and over.

U have a central api that serves sessions, u can add, renew, and serve easy. On top of that this REST API's modularity causes u to allow for easy expansion adding your own route aka easier profile pic upload and etc...

It's a MITM (Man in the middle) api between u and IMVU, but u can self host it knowing that info is only on your device unless u make the api public to the world for others to access it.
Posted 10 months ago
Thanks for information

Create an account or sign in to comment

You need to be a member in order to leave a comment

Sign in

Already have an account? Sign in here

SIGN IN NOW

Create an account

Sign up for a new account in our community. It's easy!

REGISTER A NEW ACCOUNT