Cloudron makes it easy to run web apps like WordPress, Nextcloud, GitLab on your server. Find out more or install now.


Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Bookmarks
  • Search
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Brand Logo

Cloudron Forum

Offical apps | Community apps | Demo | Docs | Install
  1. Cloudron Forum
  2. Cubby
  3. REST API broken since last update

REST API broken since last update

Scheduled Pinned Locked Moved Cubby
7 Posts 3 Posters 87 Views 3 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • S Offline
    S Offline
    shrey
    wrote last edited by shrey
    #1

    I had been using Cubby via its REST API, without any issues until July 18th, before the last update.
    But now, i'm getting Auth errors everytime.
    As far as i can remember, i had used an App Password (scoped to Cubby) earlier.

    1 Reply Last reply
    0
    • S shrey marked this topic as a question
    • S shrey marked this topic as a regular topic
    • nebulonN Offline
      nebulonN Offline
      nebulon
      Staff
      wrote last edited by
      #2

      Nothing really changed auth wise in the last cubby release. Do you see any errors in the back or frontend? Also how is auth performed? Are you using the OpenID issued access token?

      1 Reply Last reply
      0
      • S Offline
        S Offline
        shrey
        wrote last edited by shrey
        #3

        @nebulon
        Compiled with assistance from Claude:


        You're right that the auth code path is unchanged. Nothing about how Cubby resolves a credential changed. What changed is how long a token survives, and that's enough to break Bearer-authenticated API use.

        Answering your questions directly first.

        What I was actually doing, since that seems unexpected

        I've been driving Cubby's REST API server-to-server since late June, as a headless file store for an automation pipeline. A dedicated service user owns one folder; that folder has a single read-only share, so published files are readable over a public link while writes stay authenticated. There is no browser and no user session anywhere in this — just an HTTP client sending Authorization: Bearer <token>, where the token is a row in the tokens table, resolved by getUserFromToken → users.getByAccessToken → tokens.get. Not the OpenID access token.

        Endpoints in use, all with that same Bearer credential:

        Purpose Call
        Write / overwrite a file POST /api/v1/files?path=/home/<folder>/<name>&overwrite=true, raw binary body with the real Content-Type
        Create a folder POST /api/v1/files?path=<path>&directory=true
        Read, list, delete, rename GET / DELETE / relocate on /api/v1/files
        Publish a per-file link POST /api/v1/shares with { path, readonly: true, ownerUsername: <service user> }
        Revoke a link DELETE /api/v1/shares?shareId=<id>
        Public read of a published file GET /api/v1/shares/<shareId>?type=raw — no auth, and this still works fine

        That last row is why the app kept looking healthy: reads go through the share and never touch the token. Only the authenticated write path broke.

        The failing request, from my client's execution log, 2026-08-08T08:41:15Z:

        POST https://<my-cubby>/api/v1/files?path=/home/<folder>/<file>.svg&overwrite=true
        Content-Type: image/svg+xml
        Content-Length: 199
        Authorization: Bearer <redacted>
        
        → 403 {"status": "Forbidden", "message": "not allowed"}
        

        Do I see errors in backend or frontend

        No. There is no frontend involved — this is server-to-server. And the backend logs nothing, because a request bearing a deleted token isn't treated as an error at all; it's treated as an anonymous request and rejected in path resolution. debugLog is off without DEBUG set, so nothing is written.

        I ran the same request with no credential at all and got a byte-identical 403. So an expired credential and an absent credential are indistinguishable to the client and invisible on the server. That's what made this hard to diagnose: my first assumption was a permissions or share-ownership problem, and my initial post here blamed the wrong credential type as a result.

        What changed

        Commit 00c42b6681d57b1a6428ad4e458fa2c1285bc937 — "Add wopi lock support for collaboration", 2026-07-21T07:14:42Z.
        https://github.com/getcubby/cubby/commit/00c42b6681d57b1a6428ad4e458fa2c1285bc937

        Alongside the WOPI lock work, it added to backend/tokens.js:

        async function cleanup(maxAgeMs) {
            const cutoff = new Date(Date.now() - maxAgeMs);
            await database.query('DELETE FROM tokens WHERE created_at < $1', [ cutoff ]);
        }
        

        and wired it up in backend/server.js:

        const TOKEN_MAX_AGE_MS = 24 * 60 * 60 * 1000;
        ...
        tokens.cleanup(TOKEN_MAX_AGE_MS)                    // at boot
        gTokenCleanupInterval = setInterval(() => {
            tokens.cleanup(TOKEN_MAX_AGE_MS)
        }, 60 * 60 * 1000);                                 // then hourly
        

        cleanup() did not exist before this commit. backend/tokens.js has nine commits in its entire history; the one before this is "Port to esm" from 2026-02-25, which was mechanical.

        I understand why it's there — getHandle calls tokens.add() on every open and every collaborator join, so an office session accumulates rows quickly and something has to reap them. That's reasonable. The issue is that tokens is one undifferentiated table, and add() writes only (id, username) — there's no column recording what a token is for, so cleanup() can only sweep everything. WOPI handles and API tokens are the same row shape, so both go.

        Which release it shipped in

        When (UTC) What
        2026-07-02 08:39 package version → 2.15.1
        2026-07-21 07:14 00c42b6 — reaper added
        2026-07-21 16:17 dc23534 — further WOPI work (touches tokens.js, but does not alter cleanup)
        2026-07-21 16:38 Renovate bumps app digest to 56b1426
        2026-07-21 17:27 package version → 2.16.0

        So the reaper is in 2.16.0 and in no earlier release.

        Where my token came from, since it's relevant. I minted it on 28 June by inserting the row directly, inside the container via cloudron exec, with a small script that did crypto.randomBytes(32).toString('hex') and then INSERT INTO tokens (id, username) VALUES ($1, $2). That is the same statement tokens.add() runs, so the row is indistinguishable from one issued through the mobile flow — but I should be straight that I put it there myself rather than obtaining it through a supported path. I went that way because at the time the source had no token TTL anywhere in it, and there was no other non-interactive way to get a credential.

        I'm reporting it because two things behind it aren't specific to me at all: the 403 that should be a 401, and a reaper that can't tell one kind of token from another.

        20 days, 12 hours of continuous validity on a single token, with timestamps on both ends. Whatever the intended lifetime is now, nothing was expiring tokens before 2.16.0.

        My client's execution history brackets it the same way. Successful writes on:

        • 1 July — 5 executions
        • 7 July — 7 executions
        • 18 July — 5 executions, last at 2026-07-18T18:08:59Z

        Then the pipeline sat idle. The next write attempt was 2026-08-08T08:41:15Z — 21 days later, across the 2.16.0 boundary — and it 403'd, as did every attempt after it until I moved to WebDAV on 9 August. There are no executions in the gap, so this isn't a case of it degrading unnoticed; the first use after the update simply failed.

        Why the response is 403 and not 401

        backend/routes/files.js, add():

        const subject = await files.translateResourcePath(req.user?.username, filePath);
        if (!subject) return next(new HttpError(403, 'not allowed'));          // line 33-34
        
        if (subject.share?.readonly) return next(new HttpError(403, 'share is read-only'));
        if (!subject.share && !req.user) return next(new HttpError(401, 'not allowed')); // line 37
        

        and backend/files.js:

        // only shares may have optional auth
        if (resource !== 'shares' && !username) return null;
        

        For a /home/... path with no resolved user, translateResourcePath returns null, so line 34 fires and the 401 on line 37 is unreachable. The 401 you already wrote for exactly this case can never be reached on /home paths — it only applies to share paths.

        Swapping those two checks (or returning 401 when !username && resource !== 'shares') would turn this class of failure from silent into immediately diagnosable, independently of anything about token lifetime.

        One thing worth checking on your side

        The cutoff is measured from mint, not from last use — tokens.get is a plain SELECT and never touches created_at, so there's no sliding window. A token dies 24 hours after it was issued no matter how actively it's being used.

        That applies to mobile tokens too. POST /api/v1/mobile/code-to-token returns:

        next(new HttpSuccess(200, {
            token: apiToken,
            user: { username, email, displayName, admin }
        }));
        

        — no expiry field, no refresh token, and no refresh path on that endpoint. So server-side, a token issued to org.getcubby.app stops resolving within 24 hours of issue, where before 2.16.0 it didn't expire at all.

        I can't see the Android app's source, so I don't know what it does when its token stops working — if it silently re-runs the pairing flow against a live IdP session, users may never notice. Flagging it in case that path isn't as silent as it needs to be now.

        What I'd ask for

        1. Record a token type at mint time and have cleanup() filter on it, so the WOPI sweep only reaps WOPI tokens. If a blanket 24-hour lifetime on all tokens is intended, that's fine — but it needs a changelog line and a doc note. The 2.16.0 entry lists the activity log, file drop, office collaboration, recents fixes and dependencies; nothing about tokens.
        2. Return 401 when a credential is presented and doesn't resolve. Three-line change, and it's correct HTTP regardless of the rest. WebDAV already gets this right — webdav.js sends WWW-Authenticate: Basic realm="Cubby" on an unauthenticated request.
        3. A supported way to obtain a durable API credential. Right now tokens.add() is reachable only from the mobile OIDC exchange and the WOPI flow; the browser UI has used cookie sessions since 0.7.0. So there's no non-interactive way to get a token, and POST /api/v1/shares is isAuthenticated, meaning share creation has no non-interactive path at all. verifyCloudronCredentials already exists in webdav.js and works — accepting App Passwords via Basic auth on /api/v1/* too would solve this cleanly, if that's less work than it looks from outside.

        I've moved my file operations to WebDAV with a Cloudron App Password, which is durable and has no token involved, so I'm not blocked. Reporting it because the silent-403 behaviour will catch others the same way, and because of the mobile app question above.

        Reproducing

        Reaper history, no instance needed:

        git clone --filter=blob:none https://github.com/getcubby/cubby.git && cd cubby
        git log -S "TOKEN_MAX_AGE_MS" --date=iso --pretty="%ad %H %s"
        git log --follow --date=iso --pretty="%ad %h %s" -- backend/tokens.js
        

        Silent 403, any instance — no credential at all:

        curl -s -w '\n%{http_code}\n' -X POST \
          'https://<cubby>/api/v1/files?path=/home/x&directory=true'
        
        1 Reply Last reply
        0
        • nebulonN Offline
          nebulonN Offline
          nebulon
          Staff
          wrote last edited by
          #4

          Please do not dump AI output directly into the forum.

          From what I got there is that the root cause is that you are createing the tokens via direct database access. That is bound to break eventually when we restructure internals. I think the correct solution would be to provide a way to create and manage api tokens via the UI. However first we have to see if this is an actual use-case for cubby to be honest. Maybe you can describe the usecase you have here as for example the mobile app we are working on on the side uses the OpenID login flow to obtain a token. That would be the best way for other UIs using the API, but not sure if this applies to your usage?

          S 1 Reply Last reply
          1
          • nebulonN nebulon

            Please do not dump AI output directly into the forum.

            From what I got there is that the root cause is that you are createing the tokens via direct database access. That is bound to break eventually when we restructure internals. I think the correct solution would be to provide a way to create and manage api tokens via the UI. However first we have to see if this is an actual use-case for cubby to be honest. Maybe you can describe the usecase you have here as for example the mobile app we are working on on the side uses the OpenID login flow to obtain a token. That would be the best way for other UIs using the API, but not sure if this applies to your usage?

            S Offline
            S Offline
            shrey
            wrote last edited by
            #5

            @nebulon said:

            Please do not dump AI output directly into the forum.

            Fair enough.


            My use-case: Headless, auth enabled filestore with link-based sharing mechanism, with a decent admin GUI. Cubby seems to fulfil most of this, except for the 'headless automation as a first-class feature' aspect. Basically, upload files/folders, and generate unique, revocable and individual share links for them, as required. Currently, i'm managing those links in another online spreadsheet tool.

            Btw, i got around to Cubby after i searched for a solution in the Cloudron App Store, and couldn't identify any useful-enough option.

            OIDC is going to be tricky due to the interactive nature of the auth mechanism, assuming that the access token issued there are short-lived.

            So yeah, an ideal solution would be a long-lived access token/API key that allows for full programmatic access to the REST API

            1 Reply Last reply
            0
            • jamesJ Offline
              jamesJ Offline
              james
              Staff
              wrote last edited by
              #6

              Hello @shrey
              So what you need is a way for users in Cubby to create auth tokens that can be used with the API, right?
              This would be a feature request.

              S 1 Reply Last reply
              0
              • jamesJ james

                Hello @shrey
                So what you need is a way for users in Cubby to create auth tokens that can be used with the API, right?
                This would be a feature request.

                S Offline
                S Offline
                shrey
                wrote last edited by
                #7

                @james said:

                So what you need is a way for users in Cubby to create auth tokens that can be used with the API, right?

                Yes, essentially.

                1 Reply Last reply
                0

                Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                With your input, this post could be even better 💗

                Register Login
                Reply
                • Reply as topic
                Log in to reply
                • Oldest to Newest
                • Newest to Oldest
                • Most Votes


                • Login

                • Don't have an account? Register

                • Login or register to search.
                • First post
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • Bookmarks
                • Search