// Package session implements session storage.
package session import ( ) // Config is subset of tg.Config. type Config struct { // Indicates that telegram is probably censored by governments/ISPs in the current region BlockedMode bool // Whether to forcefully try connecting using IPv6 dcOptions¹ // // Links: // 1) https://core.telegram.org/type/DcOption ForceTryIpv6 bool // Current date at the server Date int // Expiration date of this config: when it expires it'll have to be refetched using help // getConfig¹ // // Links: // 1) https://core.telegram.org/method/help.getConfig Expires int // Whether we're connected to the test DCs TestMode bool // ID of the DC that returned the reply ThisDC int // DC IP list DCOptions []tg.DCOption // Domain name for fetching encrypted DC list from DNS TXT record DCTxtDomainName string // Temporary passport¹ sessions // // Links: // 1) https://core.telegram.org/passport // // Use SetTmpSessions and GetTmpSessions helpers. TmpSessions int // DC ID to use to download webfiles¹ // // Links: // 1) https://core.telegram.org/api/files#downloading-webfiles WebfileDCID int } // ConfigFromTG converts tg.Config to Config. // // Note that Config is the subset of tg.Config, so data loss is possible. func ( tg.Config) Config { return Config{ BlockedMode: .BlockedMode, ForceTryIpv6: .ForceTryIpv6, Date: .Date, Expires: .Expires, TestMode: .TestMode, ThisDC: .ThisDC, DCOptions: .DCOptions, DCTxtDomainName: .DCTxtDomainName, WebfileDCID: .WebfileDCID, TmpSessions: .TmpSessions, } } // TG returns tg.Config from Config. // // Note that config is the subset of tg.Config, so some fields will be unset. func ( Config) () tg.Config { return tg.Config{ BlockedMode: .BlockedMode, ForceTryIpv6: .ForceTryIpv6, Date: .Date, Expires: .Expires, TestMode: .TestMode, ThisDC: .ThisDC, DCOptions: .DCOptions, DCTxtDomainName: .DCTxtDomainName, WebfileDCID: .WebfileDCID, TmpSessions: .TmpSessions, } } // Data of session. type Data struct { Config Config DC int Addr string AuthKey []byte AuthKeyID []byte Salt int64 } // Storage is secure persistent storage for client session. // // NB: Implementation security is important, attacker can abuse it not only for // connecting as authenticated user or bot, but even decrypting previous // messages in some situations. type Storage interface { LoadSession(ctx context.Context) ([]byte, error) StoreSession(ctx context.Context, data []byte) error } // ErrNotFound means that session is not found in storage. var ErrNotFound = errors.New("session storage: not found") // Loader wraps Storage implementing Data (un-)marshaling. type Loader struct { Storage Storage } type jsonData struct { Version int Data Data } const latestVersion = 1 // Load loads Data from Storage. func ( *Loader) ( context.Context) (*Data, error) { , := .Storage.LoadSession() if != nil { return nil, errors.Wrap(, "load") } if len() == 0 { return nil, ErrNotFound } var jsonData if := json.Unmarshal(, &); != nil { return nil, errors.Wrap(, "unmarshal") } if .Version != latestVersion { // HACK(ernado): backward compatibility super shenanigan. return nil, errors.Wrapf(ErrNotFound, "version mismatch (%d != %d)", .Version, latestVersion) } return &.Data, } // Save saves Data to Storage. func ( *Loader) ( context.Context, *Data) error { := jsonData{ Version: latestVersion, Data: *, } , := json.Marshal() if != nil { return errors.Wrap(, "marshal") } if := .Storage.StoreSession(, ); != nil { return errors.Wrap(, "store") } return nil }