mdouchement / standardfile

Yet Another Standardfile (standardnotes server) Implementation written in Golang
MIT License
80 stars 9 forks source link

Subscriptions #74

Closed SerialDestructor closed 1 year ago

SerialDestructor commented 2 years ago

The developers of StandardNotes have provided an official way to enable the 'Pro' subscription features on self-hosted instances:

https://docs.standardnotes.com/self-hosting/subscriptions/

Is it possible to build this into your server?

mdouchement commented 2 years ago

It may be implemented with a fake API path returning the expected "Pro plan" payload to the frontend rather that implementing the whole subscription feature which has no meaning for this server. But it will take hours of work to analyze and support this feature only for three "Note Types" which I don't really need. (An nginx in front of the server should also be able to return the expected payload)

There is also this statement in the doc to consider:

Building Standard Notes has high costs. If everyone evaded contributing financially, we would no longer be here to continue to build upon and improve these services for you. Please consider donating if you do not plan on purchasing a subscription.

For the moment I will try to fix the refresh session issue and other issues.

PS: This project is opened to contributions.

SerialDestructor commented 2 years ago

I looked into it. The StandardNotes app tries to retrieve the subscription data from v2/subscriptions. This is a valid response:

{
  "meta": {
    "auth": {
      "userUuid": "01170b7c-6b5c-480d-b3dd-2a619195fc56",
      "roles": [
        {
          "uuid": "e3760e5a-7d10-417a-aa68-df3afa3a0dab",
          "name": "PRO_USER"
        },
        {
          "uuid": "6468d154-36aa-453d-baf5-708d221351c3",
          "name": "BASIC_USER"
        }
      ]
    }
  },
  "data": {
    "success": true,
    "user": {
      "uuid": "01170b7c-6b5c-480d-b3dd-2a619195fc56",
      "email": "test@notes.user"
    },
    "subscription": {
      "uuid": "e7f7e5db-82ec-4a1f-a827-62191fc7d564",
      "planName": "PRO_PLAN",
      "endsAt": 8640000000000000,
      "createdAt": 0,
      "updatedAt": 0,
      "cancelled": 0,
      "subscriptionId": 1
    }
  }
}

Apart from the userUuid, each 'role' has a uuid (I assume on a per-user basis) and each subscription has a uuid, I assume a per user one, as it's only the name that is being checked.

I think BASIC_USER is the same as a 'free' user, so basically every user at least has this tier.

A user who has only the BASIC_USER tier returns data without a subscription (only success and user in that case).

It also tries to access v1/users/{uuid}/subscription, which returns:

subscriptions getaddrinfo EAI_AGAIN payments

The latter one seems like something only used on the hosted instance for payment info, which is not applicable to self-hosted users. The functions responsible for these requests can be found here.

So to implement it, it would take to:

I think the environment variable is the most easy option, while the custom subscription enabler (maybe with a reference that kindly asks to donate something to Standard Notes) being the most 'ethical' way.

valantur commented 2 years ago

Very interesting, thanks for looking into this @SerialDestructor !

My Standard File server is running behind a caddy reverse proxy, and it could be set up to serve static content like a JSON file at v2/subscriptions if necessary. However, it seems that I would need to know the UUID of every user and also I'm concerned about exposing the username since it's part of the answer in plain text. Or is this encrypted somehow?

Can any of the options you enumerate be implemented by end-users like me without adding code to this project? I wish I knew how to code :-)

SerialDestructor commented 2 years ago

However, it seems that I would need to know the UUID of every user and also I'm concerned about exposing the username since it's part of the answer in plain text. Or is this encrypted somehow?

No, the response is different for each user account. With a static JSON file, the email address of the notes account is there in plain text, for everyone to see who knows the endpoint.

Can any of the options you enumerate be implemented by end-users like me without adding code to this project? I wish I knew how to code :-)

Well, as you mentioned, it would be possible to do it hard coded for one account and it could work, but again, your account email address and uuid will be exposed. It would stay away from that option.

I have coding expierence, but not in GoLang. I could give it a try nevertheless, but there are a lot of project-specific things (such as restricted vs non-restricted endpoints, which both seem only accessible after a login) which I don't know the difference about. Some insights about where to look would certainly help.

mdouchement commented 2 years ago

"restricted" routes need authentication (Authorization header in request).


I tried to dig in this feature, using the standalone docker-compose and app.standardnotes.org:


I tried to fake GET /v1/users/:id/subscription with data.user.email/data.user.uuid/meta.auth.userUuid updated and GET /v2/subscriptions with the same 500 Internal Server Error payload but it does not work, GET /v1/users/:id/features is never requested. I don't know what I'm missing here.

SerialDestructor commented 2 years ago

I was already experimenting with this, but since I don't know Go to well and needed to do some database changes (not familiar with BoltDB either), it was taking up too much time for the time being.

@mdouchement could you share what you tried? I will see if I can look into that as soon as I have some time.

mdouchement commented 2 years ago

Yeah sure, here my sandbox https://github.com/mdouchement/standardfile/compare/subscription-sandbox?expand=1

valantur commented 2 years ago

I can't pretend I haven't been checking this thread daily, hoping for an update from you :-)

BobWs commented 2 years ago

Maybe this will help understand how it works? https://github.com/standardnotes/standalone/issues/64#issuecomment-1064719310

BobWs commented 1 year ago

With standardnotes you only have to add these to the database to unlock the pro feature.


INSERT INTO user_roles (role_uuid , user_uuid) VALUES ( ( select uuid from roles where name="PRO_USER" order by version desc limit 1 ) ,( select uuid from users where email="<EMAIL@ADDR>" )  ) ON DUPLICATE KEY UPDATE role_uuid = VALUES(`role_uuid`);

insert into user_subscriptions set uuid = UUID() , plan_name="PRO_PLAN" , ends_at = 8640000000000000, created_at = 0 , updated_at = 0,user_uuid= (select uuid from users where email="<EMAIL@ADDR>") , subscription_id=1;