Skip to content

fix: reject a Ring token that carries no credential - #2

Open
pkeenan87 wants to merge 1 commit into
bitshaker:mainfrom
pkeenan87:fix/validate-refresh-token
Open

pkeenan87 wants to merge 1 commit into
bitshaker:mainfrom
pkeenan87:fix/validate-refresh-token

Conversation

@pkeenan87

Copy link
Copy Markdown

A failed sign-in is saved as a success

status treats any non-empty ring_token as proof of a working sign-in, and finish saves whatever getAuth returns. Neither can detect the one failure that actually matters.

Ring wraps a refresh token as base64 JSON — the credential in rt, next to the hardware id. When a sign-in does not yield a token, ring-client-api still returns a wrapped value, but rt is undefined and JSON.stringify silently drops it:

{"hid":"1906d565-…"}

That string is truthy, so:

  • finish saves it and starts the camera service
  • the panel reports a signed-in account
  • ring-mqtt then fails every call with 401 invalid_grant
  • and the panel offers no way out, because as far as it knows you are already signed in

The existing if (!auth.refresh_token) throw guard cannot catch this — the field is always a non-empty string.

This is what made #1 so hard to diagnose. The backend was healthy, all four services were running, state.json said "connected": true, and ring-mqtt logged nothing at all (everything there routes through debug, and the unit sets no DEBUG). From the outside it looked like a working install with no cameras. It took decoding the stored token to see that it contained no credential.

What this changes

Decode the token and require a non-empty rt:

  • status reports an unusable token as not authenticated, so the panel prompts for sign-in again instead of showing a dead connected state.
  • finish surfaces a clear error and leaves no state file behind, instead of persisting a token that cannot work and starting the camera service.

A value that is not wrapped JSON is still treated as usable, matching the fallback in ring-client-api's own parseAuthConfig, so raw tokens and the existing fixtures keep working.

Tests

Three added, 47/47 passing:

  • unit coverage for the wrapped/unwrapped/missing-rt cases
  • status does not report a credential-less token as authenticated
  • interactive emits an error, does not start the service, and writes no state file

The second and third would both have failed before this change.

This is independent of #1 — worth having either way, since it turns an hour of digging into an immediate, accurate error message.

🤖 Generated with Claude Code

`status` treated any non-empty `ring_token` as proof of a working sign-in,
and `finish` saved whatever `getAuth` returned. Neither can detect the one
failure that matters.

Ring wraps a refresh token as base64 JSON: the credential in `rt` next to
the hardware id. When a sign-in does not actually yield a token,
ring-client-api still returns a wrapped value, but `rt` is `undefined` and
`JSON.stringify` drops it, leaving `{"hid":"…"}`. That string is truthy, so
the existing checks accept it, the panel reports a signed-in account, and
ring-mqtt then fails every authentication attempt against the Ring API with

    401 invalid_grant - token is invalid or does not exists

The panel offers no way out, because as far as it knows the user is already
signed in.

Decode the token and require a non-empty `rt` instead:

- `status` reports an unusable token as not authenticated, so the panel
  prompts for sign-in again rather than showing a dead connected state.
- `finish` surfaces an error and leaves no state file behind, instead of
  saving a token that cannot work and starting the camera service.

A value that is not wrapped JSON is still treated as usable, matching the
fallback in ring-client-api's own `parseAuthConfig`, so raw tokens and the
existing fixtures keep working.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant