Skip to content

drive.file OAuth scope blocks editing pre-existing Docs the user owns #73

@jtsternberg

Description

@jtsternberg

drive.file OAuth scope blocks editing pre-existing Docs the user owns

Summary

ExtraSuite hardcodes the https://www.googleapis.com/auth/drive.file scope (see client/src/extrasuite/client/credentials.py:55-61). Per Google's docs, drive.file only grants access to:

  • Files the app itself created, and
  • Files the user explicitly opens via the Google Picker UI.

This means a user who has authenticated ExtraSuite via the new zero-config gws path (#70 / c131c2d6) cannot pull or push any Doc they didn't create with ExtraSuite — even Docs they own outright in their own Drive.

extrasuite docs pull <existing_doc_url> returns:

Error: Document not found. Check the ID and sharing permissions.

…which is technically true at the API level but misleading: the Doc exists, the user owns it, the user is properly authenticated. The token simply doesn't have scope to see it.

Why this is a real blocker

The whole pull → edit → push workflow is most useful for Docs that already exist — long-lived shared docs, team SOPs, coaching notes, project briefs, reference material. The current scope makes ExtraSuite unable to operate on any of those.

Workaround a user can do today: source a token with broader scope from a sibling tool. With gws (which requests full drive) installed:

export GOOGLE_WORKSPACE_CLI_TOKEN=$(gws auth export --unmasked | python3 -c "
import sys, json, urllib.request, urllib.parse
raw = sys.stdin.read(); creds = json.loads(raw[raw.find('{'):])
data = urllib.parse.urlencode({
    'client_id': creds['client_id'],
    'client_secret': creds['client_secret'],
    'refresh_token': creds['refresh_token'],
    'grant_type': 'refresh_token',
}).encode()
print(json.loads(urllib.request.urlopen('https://oauth2.googleapis.com/token', data=data).read())['access_token'])
")
extrasuite docs pull <existing_doc_url> ./out

Once the token is bearer-injected via layer 3 of the resolution chain, the operation succeeds. But:

  • This only works because gws happens to ship a refresh-token export.
  • Tokens expire after ~1 hour, so this has to be re-run each session.
  • It silently bypasses ExtraSuite's own credential cache.
  • Most users won't know to do it, and the error message doesn't suggest it.

Suggested fixes (in increasing order of intrusiveness)

  1. Improve the error message. Detect 404 on a Doc URL the user passed by ID and surface the scope explanation: "ExtraSuite uses drive.file scope, which can only see Docs ExtraSuite created. To edit a pre-existing Doc, share it with the picker or run with broader scope (see #X)."
  2. Add an opt-in --full-drive-scope flag (or EXTRASUITE_DRIVE_SCOPE=full env var). When set, request https://www.googleapis.com/auth/drive instead of drive.file. User goes through OAuth consent once, accepts the broader scope, and ExtraSuite can pull any Doc they own. Keep drive.file as the default to preserve the security posture promised in the README.
  3. Layer 3.5: trust pre-obtained GOOGLE_WORKSPACE_CLI_TOKEN regardless of stored scopes — already implemented as far as I can tell; the bridge above works. Just call it out in docs alongside Zero-config auth: work automatically when gws or gogcli is already set up #70.
  4. Direct gws/gogcli token bridge. Since the credential chain already knows where gws's OAuth client config lives (Zero-config auth: work automatically when gws or gogcli is already set up #70), it could optionally exchange gws's refresh token for an access token on every run rather than only using gws's client credentials to mint ExtraSuite-scoped tokens. Adds one HTTP round-trip per invocation; eliminates the user-side workaround.

(2) seems like the smallest cost-to-value: keeps drive.file as default for greenfield use, gives existing-Doc users a one-line escape hatch without bridging through another tool.

Environment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions