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
- 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
commontenant. - 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. - Configure delegated Microsoft Graph permissions
User.ReadandFiles.ReadWrite.AppFolder; login also requestsoffline_access. An organization's consent policy may require administrator approval. - 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
- Create/select a Google Cloud project and enable the Google Drive API.
- Configure the OAuth consent screen. If the app is in Testing mode, add your
Google account as a test user. Request
drive.appdataplusopenid,email, andprofilefor account identification. - Create an OAuth client of type Desktop app, then download its JSON file.
The CLI expects the downloaded
installedsection, not a Web client config. - 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:
- Microsoft authorization code flow
- OneDrive application folder
- Google installed-app OAuth
- Google Drive application data
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.