Memories / Documentation

On this page

OneDrive and Google Drive authorization

The CLI supports browser login, encrypted local account storage, status, local logout, and cloud sync of immutable ciphertext objects through OneDrive or Google Drive application folders. Token refresh is persisted before later uploads. Live-account verification is still pending. One account per provider can be connected on each device; signing in again replaces that provider's account only after the new authorization succeeds.

Before logging in

Build/install the new CLI and restart the daemon with the same new version:

cargo install --path crates/cli --locked
memories daemon

Run the daemon in a separate terminal. In the terminal where login will run, unlock your existing library if needed, then run memories sudo. Keep using the same --home/MEMORIES_HOME for every command. A fresh library needs memories init first. Do not move its database into a cloud-synchronized directory.

OAuth application registrations are not bundled. Use your own registrations; the CLI never asks for your Microsoft or Google account password. Authentication and consent happen on the provider's website in your browser.

OneDrive registration

  1. In Microsoft Entra ID, create an App registration. Select support for accounts in any organizational directory and personal Microsoft accounts if you need both business and personal OneDrive. This client uses the common tenant.
  2. Under Authentication, add a Mobile and desktop applications platform with redirect URI http://localhost/oauth/callback. This is a public desktop client, not a Web or SPA application. Do not create a client secret for this flow.
  3. Configure delegated Microsoft Graph permissions User.Read and Files.ReadWrite.AppFolder; login also requests offline_access. An organization's consent policy may require administrator approval.
  4. Copy the Application (client) ID and run:
memories sudo
memories cloud login onedrive --client-id YOUR_MICROSOFT_CLIENT_ID

The callback uses http://localhost:PORT/oauth/callback. Microsoft ignores the port for matching localhost redirect URIs; the path must match the registration. The listener binds IPv4 loopback only. The optional --port PORT selects a fixed port; otherwise the OS chooses a free port.

Alternatively, --client-config PATH accepts a JSON object with client_id. Custom authorization/token URLs and confidential-client secrets are not accepted.

Google Drive registration

  1. Create/select a Google Cloud project and enable the Google Drive API.
  2. Configure the OAuth consent screen. If the app is in Testing mode, add your Google account as a test user. Request drive.appdata plus openid, email, and profile for account identification.
  3. Create an OAuth client of type Desktop app, then download its JSON file. The CLI expects the downloaded installed section, not a Web client config.
  4. Keep the JSON outside this repository and run:
memories sudo
memories cloud login google-drive --client-config /path/to/google-desktop-client.json

Windows CMD example:

memories cloud login google-drive --client-config "%USERPROFILE%\Downloads\google-desktop-client.json"

The callback uses http://127.0.0.1:PORT/oauth/callback. The Desktop application flow supports loopback callbacks; no hosted callback server is needed. Downloaded endpoint fields are ignored: the client uses fixed official Google endpoints. The installed-app client secret, when supplied, is read from the JSON, never a command argument. Desktop app secrets are not a substitute for PKCE.

Google may expire refresh tokens for applications in Testing mode (commonly after seven days when non-basic scopes are requested). Reauthorize when required; publishing and provider verification requirements depend on your application.

Status, cancellation, and logout

memories cloud status
memories cloud sync onedrive
memories cloud sync google-drive
memories sudo
memories cloud logout onedrive
memories cloud logout google-drive

Status returns provider, account identity, display name/email, access-token expiry, and whether a refresh token is stored. It never returns access tokens, refresh tokens, or client secrets. sync_enabled indicates that at least one cloud account is connected. object_sync reports outbox/inbox counts only. cloud sync refreshes tokens when needed before provider transfers; an expired access token alone does not mean that provider consent has been revoked. Expiry is measured conservatively from the start of the token exchange; slow network responses or account-profile queries do not extend token validity.

Logout removes the local account and cancels any pending login for that provider. It does not revoke consent on Microsoft's or Google's servers. Use the provider's connected-app settings for remote revocation. Removing local records also does not guarantee forensic erasure from storage snapshots or older local copies.

Login has a five-minute limit. --no-browser prints the authorization URL without opening the browser, but still waits for a callback on this computer. This is not an out-of-band/manual-code flow and is not a remote/headless login mechanism. On timeout or a normal error, the CLI cancels its pending ticket. After forcefully terminating the CLI, wait for expiry or use cloud logout PROVIDER before retrying.

Security and lifecycle

  • OAuth uses authorization code with S256 PKCE and an unpredictable state value. Callback method, Host, origin, path, duplicate fields, state, expiry, and replay are checked. Provider errors and callback pages do not echo codes or tokens.
  • Browser waiting, token exchange, and identity queries run in the CLI process outside the daemon's mutex and database transactions. HTTPS requests have bounded timeouts/responses, disable automatic redirects, and do not inherit environment proxies. Network environments requiring a proxy are not supported by this initial transport configuration.
  • The daemon issues a short-lived ticket bound to provider, client ID, and local caller. Saving requires sudo again; locking, restarting, or logging out invalidates pending tickets. A late HTTP result cannot restore a cancelled login.
  • The local CLI is trusted to perform the provider exchange. The service's token-write operation requires a valid ticket and elevation; it is not a remote endpoint and cannot be used by MCP. There is no token-read command.
  • Accounts and tokens are inside encrypted local state. They are excluded from exported backups, device approval, vault synchronization, audit output, and MCP. Each device must log in separately. Cloud login does not grant vault membership.
  • Owned secret buffers are zeroized; HTTP/URL/JSON libraries and OS memory may retain transient copies, as described in SECURITY.md.

The state field is backward-compatible when absent. Do not reopen and rewrite an authorized store with an older daemon: it does not understand cloud accounts and may discard the new field, requiring another cloud login.

Validation scope

Tests exercise both provider exchanges against local HTTP fixtures, real loopback callbacks, encrypted persistence, caller binding, replay, cancellation after lock, missing scopes/refresh tokens, error redaction, backup exclusion, and MCP denial. The test transport is private to the module; production endpoints are fixed HTTPS.

Real-account authorization requires registered client IDs/configurations and user consent. No live provider account is claimed verified without that test. Mobile uses the native authorization flow described below; live consent remains unverified.

References checked on 2026-09-09:

Mobile client

The cloud button opens connection status, Connect/Reconnect, Sync now and Disconnect. Sync is foreground-only. AppAuth opens the system browser, validates state and generates PKCE. Browser backgrounding locks the library. On return, unlock and authorize locally again before Rust exchanges the code. Access and refresh tokens are verified/stored by Rust and never returned to Dart.

Build with your platform-specific public-client registration:

  • --dart-define=MEMORIES_ONEDRIVE_CLIENT_ID=...
  • --dart-define=MEMORIES_ONEDRIVE_REDIRECT_URI=app.memories.memories:/oauth/callback
  • --dart-define=MEMORIES_GOOGLE_CLIENT_ID=...
  • --dart-define=MEMORIES_GOOGLE_REDIRECT_URI=...

Unset client IDs disable login with an explanatory message. Do not embed a web client secret or reuse the desktop loopback configuration. Use the exact native redirect supported by the provider for your platform, bundle/package and signing identity. Google iOS registrations commonly use the reversed client-ID scheme; Google Android registration and redirect restrictions differ and must be verified with that provider's supported native flow before distributing an Android build. A configured URI alone is not proof that provider consent is supported.

The default registered app scheme is app.memories.memories. For another custom scheme, Android accepts Gradle property memoriesOAuthScheme (for example through ORG_GRADLE_PROJECT_memoriesOAuthScheme); iOS uses MEMORIES_OAUTH_SCHEME in mobile/ios/Flutter/Memories.xcconfig. The scheme must match the native callback registration and the Dart redirect URI. When enabling both providers, register compatible callbacks or add the second scheme to the native manifest/URL types. HTTPS app/universal links require their platform association files and native configuration; they are not enabled by the default custom-scheme build.

A completed browser login cannot restore an old elevation session. Cancelling the local password prompt leaves cloud authorization unchanged. Lock/background cancels applying late sync results. Network failures keep queued data for a later Sync now; the UI distinguishes a completed operation from a bounded run with pending work. Signing out removes local tokens, not provider consent or cloud files. See release gates for live-provider validation.