account

package
v0.0.0-...-9bcff37 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 6, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package account provides Proton Account-specific types and operations.

Index

Constants

View Source
const CookieDomain = "proton.me"

CookieDomain is the domain used for all Proton session cookies. The server sets Domain=proton.me on Set-Cookie headers, making cookies valid for all *.proton.me subdomains. We use this constant when loading persisted cookies back into a jar so the domain scoping is preserved.

Variables

View Source
var (
	// ErrSessionExpired indicates that the session tokens have expired
	// and the session cannot be restored without re-authentication.
	ErrSessionExpired = errors.New("account: session expired")

	// ErrAuthFailed indicates that authentication failed (bad credentials,
	// SRP proof mismatch, or server rejection).
	ErrAuthFailed = errors.New("account: authentication failed")

	// ErrHVRequired indicates that the server requires human verification
	// (CAPTCHA) before the request can proceed.
	ErrHVRequired = errors.New("account: human verification required")

	// ErrForkFailed indicates that the session fork protocol failed.
	ErrForkFailed = errors.New("account: session fork failed")
)

Functions

func CookieFork

func CookieFork(ctx context.Context, acctSession *api.Session, acctConfig *api.SessionCredentials, targetService api.ServiceConfig, _ string, keyPass []byte, cookieStore api.SessionStore) (*api.Session, []byte, error)

CookieFork performs a cookie-aware fork for CookieAuth services.

The flow:

  1. Load or create a CookieSession from cookieStore.
  2. If no valid cookie session exists, fork a TEMPORARY session from account (Bearer), transition it to cookies, and save.
  3. Use CookieSession.DoJSON for the fork push (AUTH cookie → full scopes).
  4. Fork pull is unchanged (unauthenticated, Session-Id only).
  5. Build child Session from fork pull response.

CRITICAL: The account Bearer session is never passed to TransitionToCookies. A temporary forked session is transitioned instead, preserving the account session for Drive operations.

func CookieLoginSave

func CookieLoginSave(cookieStore, accountStore api.SessionStore, session *api.Session, cookieSess *CookieSession, keypass []byte) error

CookieLoginSave persists both the cookie session and account metadata after a login-time cookie transition. The cookieStore receives the serialized cookies, and the accountStore receives CookieAuth=true with empty Bearer tokens (they are invalid after transition).

func CookieSRPAuth

func CookieSRPAuth(ctx context.Context, cs *CookieSession, username string, password []byte) (*proton.Auth, error)

CookieSRPAuth performs SRP authentication within a cookie session. It calls auth/info to get SRP parameters, computes proofs via go-srp, submits the proof to auth, and verifies the server's proof. Returns the Auth response containing UID, tokens, 2FA status, and password mode.

func CookieSessionFromForkPull

func CookieSessionFromForkPull(ctx context.Context, pull *ForkPullResp, svc api.ServiceConfig, cookieJar http.CookieJar) *api.Session

CookieSessionFromForkPull constructs a Session that uses cookie auth instead of Bearer auth. The provided cookie jar must contain the AUTH-<uid> cookie. CookieTransport strips the Bearer header that Resty adds, so the server only sees cookie auth.

func CookieSessionRestore

func CookieSessionRestore(ctx context.Context, options []proton.Option, cookieStore api.SessionStore, acctConfig *api.SessionCredentials, managerHook func(*proton.Manager)) (*api.Session, error)

CookieSessionRestore restores a cookie-mode session from the cookie store. It loads persisted cookies, optionally performs a proactive refresh, builds a proton.Manager with CookieTransport, and unlocks keyrings. The returned Session uses cookie auth for all Resty-based API calls (GetUser, GetAddresses, etc.). Session.Auth holds the UID but empty Bearer tokens.

func CookieTwoFA

func CookieTwoFA(ctx context.Context, cs *CookieSession, code string) error

CookieTwoFA submits a TOTP 2FA code within a cookie session.

func EncryptForkBlob

func EncryptForkBlob(blob *ForkBlob) (ciphertext string, key []byte, err error)

EncryptForkBlob encrypts a ForkBlob using AES-256-GCM with a random 32-byte key. Returns the base64-encoded ciphertext (nonce || ciphertext) and the raw key. The additional data is the UTF-8 bytes of "fork", matching WebClient v3 format.

func ForkSession

func ForkSession(ctx context.Context, parent *api.Session, targetService api.ServiceConfig, version string) (*api.Session, []byte, error)

ForkSession creates a child session for targetService by forking from the parent session. Both push and pull go to the target service's host — the parent's auth headers authenticate the push, and the selector authenticates the pull.

The parent session must be authenticated (valid UID/AccessToken). The child session is returned with BaseURL and AppVersion set to the target service's values. The decrypted SaltedKeyPass from the fork blob is returned as the second value.

func ForkSessionWithKeyPass

func ForkSessionWithKeyPass(ctx context.Context, parent *api.Session, targetService api.ServiceConfig, version string, keyPass []byte) (*api.Session, []byte, error)

ForkSessionWithKeyPass creates a child session, encrypting the given SaltedKeyPass in the fork blob instead of using the parent's UID.

func IsStale

func IsStale(accountRefresh, serviceRefresh time.Time) bool

IsStale reports whether a service session is stale relative to the account session. A service session is stale when the account's LastRefresh is after the service's LastRefresh, or when the service's LastRefresh is zero.

func NeedsCookieRefresh

func NeedsCookieRefresh(lastRefresh time.Time) bool

NeedsCookieRefresh reports whether the cookie session's LastRefresh age exceeds CookieRefreshAge. A zero-valued LastRefresh always triggers refresh.

func NeedsProactiveRefresh

func NeedsProactiveRefresh(lastRefresh time.Time) bool

NeedsProactiveRefresh reports whether the session's LastRefresh age exceeds ProactiveRefreshAge. A zero-valued LastRefresh always triggers refresh.

func NewAuthHandler

func NewAuthHandler(store api.SessionStore, session *api.Session) proton.AuthHandler

NewAuthHandler returns a proton.AuthHandler that persists updated tokens and cookies to the session store. Uses the session's own cookie jar.

func NewDeauthHandler

func NewDeauthHandler() proton.Handler

NewDeauthHandler returns a proton.Handler that logs deauth events.

func NewProtonCookieJar

func NewProtonCookieJar(cookies []api.SerialCookie, baseURL string) http.CookieJar

NewProtonCookieJar creates a new cookie jar and injects the given cookies with Proton domain scoping. Used for session restore.

func PopulateAccountCache

func PopulateAccountCache(uid string, user proton.User, addrs []proton.Address)

PopulateAccountCache creates a temporary ObjectCache scoped to the given UID and stores the provided User and Address data. This is a convenience for callers (e.g., login flows) that do not hold a Client but need to populate the account cache.

func ReadySession

func ReadySession(ctx context.Context, options []proton.Option, store api.SessionStore, cookieStore api.SessionStore, managerHook func(*proton.Manager)) (*api.Session, error)

ReadySession restores a session from the store, registers auth/deauth handlers, and returns a fully initialized Session ready for use. This is the recommended entry point for consumers that need an authenticated session. When cookieStore is non-nil and the session has CookieAuth=true, the cookie restore path is used.

func RestoreServiceSession

func RestoreServiceSession(ctx context.Context, service string, options []proton.Option, store api.SessionStore, accountStore api.SessionStore, cookieStore api.SessionStore, version string, managerHook func(*proton.Manager)) (*api.Session, error)

RestoreServiceSession restores or creates a service-specific session. If no session exists for the service, it forks from the account session. If no account session exists, it returns ErrNotLoggedIn.

The flow:

  1. Load account session config from accountStore.
  2. If CookieAuth=true, use cookie fork path (CookieSessionRestore → cookieFork).
  3. Otherwise, build account session from credentials.
  4. If account session age > ProactiveRefreshAge, trigger proactive refresh.
  5. If service session missing or stale (account LastRefresh > service LastRefresh), fork from account session via ForkSessionWithKeyPass.
  6. Set session.BaseURL and AppVersion from the ServiceConfig.
  7. Return session.

func SessionFromCredentials

func SessionFromCredentials(ctx context.Context, options []proton.Option, config *api.SessionCredentials, managerHook func(*proton.Manager)) (*api.Session, error)

SessionFromCredentials initializes a new session from the provided config. The session is not fully usable until it has been Unlock'ed using the user-provided keypass.

func SessionFromForkPull

func SessionFromForkPull(ctx context.Context, pull *ForkPullResp, svc api.ServiceConfig, _ string) *api.Session

SessionFromForkPull constructs a Session from a ForkPullResp and ServiceConfig. The version string is passed through for backward compatibility but the service's own app version is used for all requests.

func SessionFromLogin

func SessionFromLogin(ctx context.Context, options []proton.Option, username string, password string, hvDetails *proton.APIHVDetails, managerHook func(*proton.Manager)) (*api.Session, error)

SessionFromLogin initializes a new session from the provided login/password. If hvDetails is non-nil, the login includes the HV token for CAPTCHA retry. The same manager (and cookie jar) is used for both initial and HV-retried login attempts — this is required because Proton's backend correlates the solved CAPTCHA with the session cookie from the initial attempt.

On error, the returned *Session is intentionally non-nil and reusable for SessionRetryWithHV. The manager and cookie jar must be preserved across attempts so that the solved CAPTCHA correlates with the session cookie established during the initial (failed) login request.

func SessionList

func SessionList(store api.SessionStore) ([]string, error)

SessionList returns account names from the session store.

func SessionRestore

func SessionRestore(ctx context.Context, options []proton.Option, store api.SessionStore, cookieStore api.SessionStore, managerHook func(*proton.Manager)) (*api.Session, error)

SessionRestore loads credentials from the store and creates an unlocked session. Returns ErrNotLoggedIn if no session is stored. When the loaded config has CookieAuth=true and cookieStore is non-nil, the cookie restore path is used instead of the Bearer path.

func SessionRetryWithHV

func SessionRetryWithHV(ctx context.Context, session *api.Session, username, password string, hv *proton.APIHVDetails) error

SessionRetryWithHV retries login on an existing session (reusing its manager and cookie jar) with HV details after the user solved the CAPTCHA. A fresh AuthInfo is fetched because the original SRP session is invalidated by the 9001 response. The solved CAPTCHA composite token is NOT bound to the SRP session — it's bound to the HumanVerificationToken.

func SessionRevoke

func SessionRevoke(ctx context.Context, session *api.Session, store api.SessionStore, force bool) error

SessionRevoke revokes the API session and deletes it from the store. If force is true, store deletion proceeds even when the API revoke fails.

func SessionSave

func SessionSave(store api.SessionStore, session *api.Session, keypass []byte) error

SessionSave persists session credentials, cookie jar state, and a refresh timestamp to the store. Uses CookieQueryURL (path=/api/auth/refresh) so the jar query matches both AUTH (path=/api/) and REFRESH (path=/api/auth/refresh) cookies.

Types

type Address

type Address struct {
	// contains filtered or unexported fields
}

Address is an opaque wrapper around a Proton email address. Consumers access fields via methods — they cannot construct or inspect the struct directly.

func (Address) Email

func (a Address) Email() string

Email returns the email address string.

func (Address) ID

func (a Address) ID() string

ID returns the Proton address ID.

func (Address) Order

func (a Address) Order() int

Order returns the address display order.

func (Address) Status

func (a Address) Status() int

Status returns the address status as an integer.

func (Address) Type

func (a Address) Type() int

Type returns the address type as an integer.

type AnonSessionResp

type AnonSessionResp struct {
	Code         int    `json:"Code"`
	UID          string `json:"UID"`
	AccessToken  string `json:"AccessToken"`
	RefreshToken string `json:"RefreshToken"`
}

AnonSessionResp is the response from POST /auth/v4/sessions.

func CreateAnonSession

func CreateAnonSession(ctx context.Context) (*AnonSessionResp, http.CookieJar, error)

CreateAnonSession creates an anonymous session on the account host. This is the first step in the browser's login flow — it creates a session with no credentials, returning UID + tokens that can be used for subsequent SRP login. The session is created on account.proton.me matching the browser's flow exactly.

type AuthCookiesReq

type AuthCookiesReq struct {
	UID          string `json:"UID"`
	RefreshToken string `json:"RefreshToken"`
	GrantType    string `json:"GrantType"`    // always "refresh_token"
	RedirectURI  string `json:"RedirectURI"`  // "https://proton.me"
	ResponseType string `json:"ResponseType"` // "token"
	State        string `json:"State"`        // random state string
}

AuthCookiesReq is the request body for POST /core/v4/auth/cookies.

type Client

type Client struct {
	Session *api.Session
	// contains filtered or unexported fields
}

Client wraps an api.Session with Account-specific operations.

func NewClient

func NewClient(session *api.Session) *Client

NewClient constructs an Account client from an existing session.

func NewClientWithCache

func NewClientWithCache(session *api.Session, uid string) *Client

NewClientWithCache constructs an Account client with an ObjectCache scoped to the given UID. The cache stores raw encrypted User and Address API objects on disk at $XDG_RUNTIME_DIR/proton/account/{uid}/. Returns a Client with caching disabled when uid is empty or $XDG_RUNTIME_DIR is not set.

func (*Client) GetAddresses

func (c *Client) GetAddresses(ctx context.Context) ([]Address, error)

GetAddresses returns all email addresses associated with the account. The returned Address values are opaque wrappers — consumers access fields via accessor methods and do not need to import go-proton-api.

func (*Client) GetUser

func (c *Client) GetUser(ctx context.Context) (User, error)

GetUser returns the authenticated user's profile and quota information. The returned User is an opaque wrapper — consumers access fields via accessor methods and do not need to import go-proton-api.

type CookieSession

type CookieSession struct {
	UID        string               // Proton UID
	BaseURL    string               // API base URL (e.g., "https://account.proton.me/api")
	AppVersion string               // x-pm-appversion header value
	UserAgent  string               // User-Agent header value
	HVDetails  *proton.APIHVDetails // when non-nil, HV headers are added to requests
	Store      api.SessionStore     // when non-nil, cookies are persisted after refresh
	// contains filtered or unexported fields
}

CookieSession holds cookie-based authentication state for services that require cookie auth (e.g., Lumo). Created from a Bearer session via TransitionToCookies, or restored from persisted cookies.

func CookieSessionFromConfig

func CookieSessionFromConfig(config *CookieSessionConfig, baseURL string) *CookieSession

CookieSessionFromConfig creates a CookieSession from persisted cookies. No proton.Manager or Resty client is created — the session uses net/http directly with the restored cookie jar. The baseURL parameter determines the domain cookies are scoped to in the jar; pass the account service host (e.g., "https://account.proton.me/api").

func NewCookieAuthHandler

func NewCookieAuthHandler(cookieConfig *api.SessionCredentials, baseURL string, transport *CookieTransport, cookieStore api.SessionStore) *CookieSession

NewCookieAuthHandler creates a CookieSession for 401 refresh and wires it into the CookieTransport. Returns the CookieSession so callers can use it for other cookie operations. The cookieStore is used to persist updated cookies after refresh.

func NewCookieSession

func NewCookieSession(uid, baseURL string, jar http.CookieJar) *CookieSession

NewCookieSession creates a CookieSession with the given parameters. Used by tests and callers that need to construct a CookieSession directly rather than via TransitionToCookies or CookieSessionFromConfig.

func NewCookieSessionForRefresh

func NewCookieSessionForRefresh(uid, baseURL, appVersion string, jar http.CookieJar, store api.SessionStore) *CookieSession

NewCookieSessionForRefresh creates a CookieSession suitable for proactive cookie refresh. Used by restoreExistingCookieService.

func TransitionToCookies

func TransitionToCookies(ctx context.Context, session *api.Session) (*CookieSession, error)

TransitionToCookies calls POST /core/v4/auth/cookies to transition from Bearer auth to cookie auth. After this call, the Bearer tokens in the source session are INVALID server-side. Returns a CookieSession with the AUTH and REFRESH cookies set in its jar.

func (*CookieSession) Config

func (cs *CookieSession) Config() *CookieSessionConfig

Config serializes the CookieSession's cookie jar into a CookieSessionConfig suitable for persistence. LastRefresh is set to the current time.

func (*CookieSession) DoJSON

func (cs *CookieSession) DoJSON(ctx context.Context, method, path string, body, result any) error

DoJSON executes an authenticated JSON API request using cookie auth. No Authorization: Bearer header is sent — auth is provided via the AUTH-<uid> cookie in the cookie jar. The x-pm-uid header is always set.

On success (Code 1000), result is populated if non-nil. On API error, returns *api.Error with Status, Code, and Message. On 401, attempts a cookie refresh via RefreshCookies and retries the request once.

func (*CookieSession) DoSSE

func (cs *CookieSession) DoSSE(ctx context.Context, path string, body any) (io.ReadCloser, error)

DoSSE executes an authenticated POST and returns the raw response body for SSE streaming. The caller is responsible for closing the returned io.ReadCloser. Sets cookie auth headers (x-pm-uid, x-pm-appversion, User-Agent) plus Accept: text/event-stream. No Authorization: Bearer header is sent. Returns an *api.Error on non-2xx HTTP status.

func (*CookieSession) RefreshCookies

func (cs *CookieSession) RefreshCookies(ctx context.Context) error

RefreshCookies calls POST /core/v4/auth/cookies with the REFRESH cookie to obtain new AUTH and REFRESH cookies. The cookie jar is updated with the new cookies from the response Set-Cookie headers. Concurrent calls are serialized via cs.mu — only one refresh HTTP call executes at a time.

CRITICAL: This method uses a raw http.Client, NOT DoJSON. Using DoJSON would cause infinite recursion because DoJSON's 401 retry calls RefreshCookies.

type CookieSessionConfig

type CookieSessionConfig struct {
	UID         string             `json:"uid"`
	Cookies     []api.SerialCookie `json:"cookies,omitempty"`
	LastRefresh time.Time          `json:"last_refresh,omitempty"`
	Service     string             `json:"service,omitempty"`
}

CookieSessionConfig holds the minimal data to restore a CookieSession without Resty. Stored separately from the Bearer SessionCredentials.

type CookieTransport

type CookieTransport struct {
	// Base is the underlying transport. If nil, http.DefaultTransport is used.
	Base http.RoundTripper
	// contains filtered or unexported fields
}

CookieTransport is an http.RoundTripper that converts Bearer auth to cookie auth. It strips the Authorization header added by Resty and relies on the cookie jar (set on the http.Client by Resty) to send AUTH-<uid> cookies instead.

When a CookieSession is attached (via SetCookieSession), the transport intercepts 401 responses, triggers a cookie refresh, and retries the request once. This handles auth expiry transparently without relying on go-proton-api's Bearer-based authRefresh (which fails for cookie sessions).

Usage:

ct := &CookieTransport{Base: http.DefaultTransport}
manager := proton.New(
    proton.WithTransport(ct),
    proton.WithCookieJar(jar),  // jar has AUTH-<uid> cookie
    ...
)

func (*CookieTransport) RoundTrip

func (ct *CookieTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip strips the Authorization header and delegates to the base transport. The cookie jar on the http.Client sends AUTH-<uid> cookies automatically. If a CookieSession is attached and the response is 401, triggers a cookie refresh and retries once.

func (*CookieTransport) SetCookieSession

func (ct *CookieTransport) SetCookieSession(cs *CookieSession, store api.SessionStore)

SetCookieSession attaches a CookieSession and store for 401 refresh. When set, 401 responses trigger RefreshCookies and a retry.

type ForkBlob

type ForkBlob struct {
	Type        string `json:"type"`        // "default"
	KeyPassword string `json:"keyPassword"` // SaltedKeyPass from parent
}

ForkBlob is the plaintext payload encrypted in the fork.

func DecryptForkBlob

func DecryptForkBlob(ciphertextB64 string, key []byte) (*ForkBlob, error)

DecryptForkBlob decrypts a base64-encoded fork blob using AES-256-GCM. The ciphertext format is nonce (12 bytes) || ciphertext || tag (16 bytes), matching WebClient v3 format.

type ForkPullResp

type ForkPullResp struct {
	Code         int      `json:"Code"`
	UID          string   `json:"UID"`
	AccessToken  string   `json:"AccessToken"`
	RefreshToken string   `json:"RefreshToken"`
	Payload      string   `json:"Payload,omitempty"`
	Scopes       []string `json:"Scopes,omitempty"`
}

ForkPullResp is the response from GET /auth/v4/sessions/forks/<selector>.

type ForkPushReq

type ForkPushReq struct {
	ChildClientID string `json:"ChildClientID"`
	Independent   int    `json:"Independent"`
	Payload       string `json:"Payload,omitempty"`
}

ForkPushReq is the request body for POST /auth/v4/sessions/forks.

type ForkPushResp

type ForkPushResp struct {
	Code     int    `json:"Code"`
	Selector string `json:"Selector"`
}

ForkPushResp is the response from POST /auth/v4/sessions/forks.

type User

type User struct {
	// contains filtered or unexported fields
}

User is an opaque wrapper around the Proton user profile. Consumers access fields via methods — they cannot construct or inspect the struct directly.

func (User) CalendarUsedSpace

func (u User) CalendarUsedSpace() int64

CalendarUsedSpace returns storage used by Calendar in bytes.

func (User) ContactUsedSpace

func (u User) ContactUsedSpace() int64

ContactUsedSpace returns storage used by Contacts in bytes.

func (User) DisplayName

func (u User) DisplayName() string

DisplayName returns the user's display name.

func (User) DriveUsedSpace

func (u User) DriveUsedSpace() int64

DriveUsedSpace returns storage used by Drive in bytes.

func (User) Email

func (u User) Email() string

Email returns the user's primary email address.

func (User) ID

func (u User) ID() string

ID returns the Proton user ID.

func (User) MailUsedSpace

func (u User) MailUsedSpace() int64

MailUsedSpace returns storage used by Mail in bytes.

func (User) MaxSpace

func (u User) MaxSpace() int64

MaxSpace returns the total storage quota in bytes.

func (User) Name

func (u User) Name() string

Name returns the Proton username.

func (User) PassUsedSpace

func (u User) PassUsedSpace() int64

PassUsedSpace returns storage used by Pass in bytes.

func (User) UsedSpace

func (u User) UsedSpace() int64

UsedSpace returns the total storage used in bytes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL