auth: complete account authorization flows

(cherry picked from commit 04f4527df32ad5c35720cccc41d27fe51549612f)
This commit is contained in:
A 2026-06-08 21:42:32 +08:00
parent af41d18478
commit 6dc42942c8
21 changed files with 1780 additions and 50 deletions

View file

@ -12,6 +12,12 @@ import (
// registerAccount 注册 account.* RPC handler。
func (r *Router) registerAccount(d *tg.ServerDispatcher) {
d.OnAccountRegisterDevice(func(ctx context.Context, req *tg.AccountRegisterDeviceRequest) (bool, error) {
return true, nil
})
d.OnAccountUnregisterDevice(func(ctx context.Context, req *tg.AccountUnregisterDeviceRequest) (bool, error) {
return true, nil
})
d.OnAccountCheckUsername(r.onAccountCheckUsername)
d.OnAccountUpdateProfile(r.onAccountUpdateProfile)
d.OnAccountUpdateUsername(r.onAccountUpdateUsername)
@ -35,11 +41,18 @@ func (r *Router) registerAccount(d *tg.ServerDispatcher) {
d.OnAccountUpdateNotifySettings(func(ctx context.Context, req *tg.AccountUpdateNotifySettingsRequest) (bool, error) {
return true, nil
})
d.OnAccountResetNotifySettings(func(ctx context.Context) (bool, error) {
return true, nil
})
d.OnAccountGetPrivacy(r.onAccountGetPrivacy)
d.OnAccountSetPrivacy(r.onAccountSetPrivacy)
d.OnAccountGetAuthorizations(func(ctx context.Context) (*tg.AccountAuthorizations, error) {
return tdesktop.Authorizations(), nil
})
d.OnAccountGetAuthorizations(r.onAccountGetAuthorizations)
d.OnAccountResetAuthorization(r.onAccountResetAuthorization)
d.OnAccountGetPasswordSettings(r.onAccountGetPasswordSettings)
d.OnAccountUpdatePasswordSettings(r.onAccountUpdatePasswordSettings)
d.OnAccountConfirmPasswordEmail(r.onAccountConfirmPasswordEmail)
d.OnAccountResendPasswordEmail(r.onAccountResendPasswordEmail)
d.OnAccountCancelPasswordEmail(r.onAccountCancelPasswordEmail)
d.OnAccountGetDefaultEmojiStatuses(func(ctx context.Context, hash int64) (tg.AccountEmojiStatusesClass, error) {
return tdesktop.DefaultEmojiStatuses(), nil
})
@ -57,18 +70,66 @@ func (r *Router) registerAccount(d *tg.ServerDispatcher) {
d.OnAccountGetContactSignUpNotification(func(ctx context.Context) (bool, error) {
return false, nil
})
d.OnAccountSetContactSignUpNotification(func(ctx context.Context, silent bool) (bool, error) {
return true, nil
})
d.OnAccountGetThemes(func(ctx context.Context, req *tg.AccountGetThemesRequest) (tg.AccountThemesClass, error) {
return tdesktop.AccountThemes(), nil
})
d.OnAccountGetRecentEmojiStatuses(func(ctx context.Context, hash int64) (tg.AccountEmojiStatusesClass, error) {
return &tg.AccountEmojiStatuses{Hash: 0, Statuses: []tg.EmojiStatusClass{}}, nil
})
d.OnAccountClearRecentEmojiStatuses(func(ctx context.Context) (bool, error) {
return true, nil
})
d.OnAccountUpdateEmojiStatus(func(ctx context.Context, emojistatus tg.EmojiStatusClass) (bool, error) {
return true, nil
})
d.OnAccountGetDefaultProfilePhotoEmojis(func(ctx context.Context, hash int64) (tg.EmojiListClass, error) {
return tdesktop.DefaultGroupPhotoEmojis(), nil
})
d.OnAccountGetDefaultBackgroundEmojis(func(ctx context.Context, hash int64) (tg.EmojiListClass, error) {
return tdesktop.DefaultGroupPhotoEmojis(), nil
})
d.OnAccountGetChannelDefaultEmojiStatuses(func(ctx context.Context, hash int64) (tg.AccountEmojiStatusesClass, error) {
return &tg.AccountEmojiStatuses{Hash: 0, Statuses: []tg.EmojiStatusClass{}}, nil
})
d.OnAccountGetChannelRestrictedStatusEmojis(func(ctx context.Context, hash int64) (tg.EmojiListClass, error) {
return tdesktop.DefaultGroupPhotoEmojis(), nil
})
d.OnAccountSetContentSettings(func(ctx context.Context, req *tg.AccountSetContentSettingsRequest) (bool, error) {
return true, nil
})
d.OnAccountGetContentSettings(func(ctx context.Context) (*tg.AccountContentSettings, error) {
return tdesktop.ContentSettings(), nil
})
d.OnAccountGetGlobalPrivacySettings(func(ctx context.Context) (*tg.GlobalPrivacySettings, error) {
return tdesktop.GlobalPrivacySettings(), nil
})
d.OnAccountSetGlobalPrivacySettings(func(ctx context.Context, settings tg.GlobalPrivacySettings) (*tg.GlobalPrivacySettings, error) {
return &settings, nil
})
d.OnAccountGetPasskeys(func(ctx context.Context) (*tg.AccountPasskeys, error) {
return tdesktop.Passkeys(), nil
})
d.OnAccountGetWebAuthorizations(func(ctx context.Context) (*tg.AccountWebAuthorizations, error) {
return tdesktop.WebAuthorizations(), nil
})
d.OnAccountResetWebAuthorization(func(ctx context.Context, hash int64) (bool, error) {
return true, nil
})
d.OnAccountResetWebAuthorizations(func(ctx context.Context) (bool, error) {
return true, nil
})
d.OnAccountGetNotifyExceptions(func(ctx context.Context, req *tg.AccountGetNotifyExceptionsRequest) (tg.UpdatesClass, error) {
return &tg.Updates{Updates: []tg.UpdateClass{}, Users: []tg.UserClass{}, Chats: []tg.ChatClass{}, Date: int(r.clock.Now().Unix())}, nil
})
d.OnAccountGetAutoDownloadSettings(func(ctx context.Context) (*tg.AccountAutoDownloadSettings, error) {
return tdesktop.AutoDownloadSettings(), nil
})
d.OnAccountSaveAutoDownloadSettings(func(ctx context.Context, req *tg.AccountSaveAutoDownloadSettingsRequest) (bool, error) {
return true, nil
})
d.OnAccountGetSavedMusicIDs(func(ctx context.Context, hash int64) (tg.AccountSavedMusicIDsClass, error) {
if _, _, err := r.currentUserID(ctx); err != nil {
return nil, internalErr()
@ -76,9 +137,121 @@ func (r *Router) registerAccount(d *tg.ServerDispatcher) {
return &tg.AccountSavedMusicIDs{IDs: []int64{}}, nil
})
d.OnAccountGetAccountTTL(r.onAccountGetAccountTTL)
d.OnAccountSetAccountTTL(func(ctx context.Context, ttl tg.AccountDaysTTL) (bool, error) {
return true, nil
})
d.OnAccountSetAuthorizationTTL(func(ctx context.Context, authorizationttldays int) (bool, error) {
return true, nil
})
d.OnAccountChangeAuthorizationSettings(func(ctx context.Context, req *tg.AccountChangeAuthorizationSettingsRequest) (bool, error) {
return true, nil
})
d.OnAccountResetPassword(r.onAccountResetPassword)
d.OnAccountDeclinePasswordReset(r.onAccountDeclinePasswordReset)
d.OnAccountUpdateStatus(r.onAccountUpdateStatus)
}
func (r *Router) onAccountGetAuthorizations(ctx context.Context) (*tg.AccountAuthorizations, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
if r.deps.Auth == nil {
return tdesktop.Authorizations(), nil
}
authKeyID, _ := AuthKeyIDFrom(ctx)
items, err := r.deps.Auth.ListAuthorizations(ctx, userID)
if err != nil {
return nil, internalErr()
}
out := &tg.AccountAuthorizations{Authorizations: make([]tg.Authorization, 0, len(items))}
for _, item := range items {
out.Authorizations = append(out.Authorizations, tgAuthorization(item, authKeyID, int(r.clock.Now().Unix())))
}
return out, nil
}
func (r *Router) onAccountResetAuthorization(ctx context.Context, hash int64) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
if r.deps.Auth == nil {
return true, nil
}
deleted, found, err := r.deps.Auth.ResetAuthorization(ctx, userID, hash)
if err != nil {
return false, internalErr()
}
if !found {
return true, nil
}
r.invalidateAuthUserCache(deleted.AuthKeyID)
r.unbindAuthKey(deleted.AuthKeyID)
_ = r.clearAuthKeyState(ctx, deleted.AuthKeyID)
return true, nil
}
func (r *Router) onAccountGetPasswordSettings(ctx context.Context, password tg.InputCheckPasswordSRPClass) (*tg.AccountPasswordSettings, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
settings, err := r.deps.Account.GetPasswordSettings(ctx, userID, domainPasswordCheck(password))
if err != nil {
return nil, passwordErr(err)
}
return tgPasswordSettings(settings), nil
}
func (r *Router) onAccountUpdatePasswordSettings(ctx context.Context, req *tg.AccountUpdatePasswordSettingsRequest) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
input, err := domainPasswordInputSettings(req.NewSettings)
if err != nil {
return false, err
}
if err := r.deps.Account.UpdatePasswordSettings(ctx, userID, domainPasswordCheck(req.Password), input); err != nil {
return false, passwordErr(err)
}
return true, nil
}
func (r *Router) onAccountConfirmPasswordEmail(ctx context.Context, code string) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
if err := r.deps.Account.ConfirmPasswordEmail(ctx, userID, code); err != nil {
return false, passwordErr(err)
}
return true, nil
}
func (r *Router) onAccountResendPasswordEmail(ctx context.Context) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
if err := r.deps.Account.ResendPasswordEmail(ctx, userID); err != nil {
return false, passwordErr(err)
}
return true, nil
}
func (r *Router) onAccountCancelPasswordEmail(ctx context.Context) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
if err := r.deps.Account.CancelPasswordEmail(ctx, userID); err != nil {
return false, passwordErr(err)
}
return true, nil
}
func (r *Router) onAccountGetPrivacy(ctx context.Context, key tg.InputPrivacyKeyClass) (*tg.AccountPrivacyRules, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
@ -140,6 +313,35 @@ func (r *Router) onAccountGetAccountTTL(ctx context.Context) (*tg.AccountDaysTTL
return &tg.AccountDaysTTL{Days: 365}, nil
}
func (r *Router) onAccountResetPassword(ctx context.Context) (tg.AccountResetPasswordResultClass, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
if r.deps.Account == nil {
return &tg.AccountResetPasswordFailedWait{RetryDate: int(r.clock.Now().Unix()) + 86400}, nil
}
result, err := r.deps.Account.ResetPassword(ctx, userID)
if err != nil {
return nil, passwordErr(err)
}
return tgPasswordResetResult(result), nil
}
func (r *Router) onAccountDeclinePasswordReset(ctx context.Context) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
if r.deps.Account == nil {
return true, nil
}
if err := r.deps.Account.DeclinePasswordReset(ctx, userID); err != nil {
return false, internalErr()
}
return true, nil
}
func (r *Router) onAccountUpdateStatus(ctx context.Context, offline bool) (bool, error) {
userID, authorized, err := r.currentUserID(ctx)
if err != nil {
@ -153,6 +355,19 @@ func (r *Router) onAccountUpdateStatus(ctx context.Context, offline bool) (bool,
return true, nil
}
func tgPasswordResetResult(result domain.PasswordResetResult) tg.AccountResetPasswordResultClass {
switch result.Kind {
case domain.PasswordResetOK:
return &tg.AccountResetPasswordOk{}
case domain.PasswordResetRequestedWait:
return &tg.AccountResetPasswordRequestedWait{UntilDate: result.UntilDate}
case domain.PasswordResetFailedWait:
return &tg.AccountResetPasswordFailedWait{RetryDate: result.RetryDate}
default:
return &tg.AccountResetPasswordFailedWait{}
}
}
func (r *Router) tgAccountPrivacyRules(ctx context.Context, viewerUserID int64, rules domain.PrivacyRules) (*tg.AccountPrivacyRules, error) {
userIDs := privacyRuleUserIDs(rules.Rules)
users := []domain.User{}
@ -532,6 +747,33 @@ func (r *Router) pushUsernameUpdate(ctx context.Context, u domain.User) {
})
}
func tgAuthorization(a domain.Authorization, currentAuthKeyID [8]byte, now int) tg.Authorization {
created := int(a.CreatedAt.Unix())
if created == 0 {
created = now
}
active := int(a.ActiveAt.Unix())
if active == 0 {
active = created
}
return tg.Authorization{
Current: a.AuthKeyID == currentAuthKeyID,
OfficialApp: true,
Hash: a.Hash,
DeviceModel: a.DeviceModel,
Platform: a.Platform,
SystemVersion: a.SystemVersion,
APIID: a.APIID,
AppName: "Telegram Desktop",
AppVersion: a.AppVersion,
DateCreated: created,
DateActive: active,
IP: a.IP,
Country: "Unknown",
Region: "Unknown",
}
}
func usernameErr(err error) error {
switch {
case errors.Is(err, domain.ErrUsernameInvalid):

View file

@ -3,6 +3,7 @@ package rpc
import (
"context"
"encoding/binary"
"errors"
"fmt"
"strings"
"time"
@ -25,10 +26,28 @@ const loginMessagePushDelay = 2 * time.Second
func (r *Router) registerAuth(d *tg.ServerDispatcher) {
d.OnAuthBindTempAuthKey(r.onAuthBindTempAuthKey)
d.OnAuthExportLoginToken(r.onAuthExportLoginToken)
d.OnAuthImportLoginToken(r.onAuthImportLoginToken)
d.OnAuthAcceptLoginToken(r.onAuthAcceptLoginToken)
d.OnAuthExportAuthorization(func(ctx context.Context, dcid int) (*tg.AuthExportedAuthorization, error) {
return nil, dcIDInvalidErr()
})
d.OnAuthImportAuthorization(func(ctx context.Context, req *tg.AuthImportAuthorizationRequest) (tg.AuthAuthorizationClass, error) {
return nil, dcIDInvalidErr()
})
d.OnAuthDropTempAuthKeys(func(ctx context.Context, exceptauthkeys []int64) (bool, error) {
return true, nil
})
d.OnAuthSendCode(r.onAuthSendCode)
d.OnAuthResendCode(r.onAuthResendCode)
d.OnAuthCancelCode(r.onAuthCancelCode)
d.OnAuthSignIn(r.onAuthSignIn)
d.OnAuthSignUp(r.onAuthSignUp)
d.OnAuthLogOut(r.onAuthLogOut)
d.OnAuthResetAuthorizations(r.onAuthResetAuthorizations)
d.OnAuthCheckPassword(r.onAuthCheckPassword)
d.OnAuthRequestPasswordRecovery(r.onAuthRequestPasswordRecovery)
d.OnAuthRecoverPassword(r.onAuthRecoverPassword)
d.OnAuthCheckRecoveryPassword(r.onAuthCheckRecoveryPassword)
}
// onAuthBindTempAuthKey 记录 TDesktop 的 PFS temp→perm auth key 绑定。
@ -69,22 +88,45 @@ func (r *Router) onAuthExportLoginToken(ctx context.Context, _ *tg.AuthExportLog
return tdesktop.LoginToken(r.clock.Now(), id, sessionID), nil
}
func (r *Router) onAuthImportLoginToken(ctx context.Context, token []byte) (tg.AuthLoginTokenClass, error) {
id, _ := AuthKeyIDFrom(ctx)
sessionID, _ := SessionIDFrom(ctx)
return tdesktop.LoginToken(r.clock.Now(), id, sessionID), nil
}
func (r *Router) onAuthAcceptLoginToken(ctx context.Context, token []byte) (*tg.Authorization, error) {
return nil, authTokenInvalidErr()
}
// onAuthSendCode 处理 auth.sendCode生成 phone_code_hash 并返回 sentCode。
func (r *Router) onAuthSendCode(ctx context.Context, req *tg.AuthSendCodeRequest) (tg.AuthSentCodeClass, error) {
hash, err := r.deps.Auth.SendCode(ctx, req.PhoneNumber)
if err != nil {
return nil, internalErr()
}
return tgSentCode(hash), nil
}
func tgSentCode(hash string) tg.AuthSentCodeClass {
return &tg.AuthSentCode{
Type: &tg.AuthSentCodeTypeApp{Length: devCodeLength},
PhoneCodeHash: hash,
}, nil
}
}
// onAuthSignIn 处理 auth.signIn校验验证码用户不存在时返回 SignUpRequired。
func (r *Router) onAuthSignIn(ctx context.Context, req *tg.AuthSignInRequest) (tg.AuthAuthorizationClass, error) {
u, loginMessage, needSignUp, err := r.deps.Auth.SignIn(ctx, r.authzFromCtx(ctx), req.PhoneNumber, req.PhoneCodeHash, req.PhoneCode)
if err != nil {
if errors.Is(err, domain.ErrSessionPasswordNeeded) && u.ID != 0 {
if err := r.clearAuthKeyStateOnUserChange(ctx, u.ID); err != nil {
return nil, internalErr()
}
if id, ok := AuthKeyIDFrom(ctx); ok {
r.setAuthUserCache(id, u.ID, true)
}
r.bindSessionUser(ctx, u.ID)
}
return nil, signInErr(err)
}
if needSignUp {
@ -102,6 +144,103 @@ func (r *Router) onAuthSignIn(ctx context.Context, req *tg.AuthSignInRequest) (t
return &tg.AuthAuthorization{User: r.tgSelfUser(u)}, nil
}
func (r *Router) onAuthResendCode(ctx context.Context, req *tg.AuthResendCodeRequest) (tg.AuthSentCodeClass, error) {
hash, err := r.deps.Auth.ResendCode(ctx, req.PhoneNumber, req.PhoneCodeHash)
if err != nil {
return nil, signInErr(err)
}
return tgSentCode(hash), nil
}
func (r *Router) onAuthCancelCode(ctx context.Context, req *tg.AuthCancelCodeRequest) (bool, error) {
if err := r.deps.Auth.CancelCode(ctx, req.PhoneNumber, req.PhoneCodeHash); err != nil {
return false, signInErr(err)
}
return true, nil
}
func (r *Router) onAuthResetAuthorizations(ctx context.Context) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
authKeyID, _ := AuthKeyIDFrom(ctx)
deleted, err := r.deps.Auth.ResetAuthorizations(ctx, userID, authKeyID)
if err != nil {
return false, internalErr()
}
for _, a := range deleted {
r.invalidateAuthUserCache(a.AuthKeyID)
r.unbindAuthKey(a.AuthKeyID)
_ = r.clearAuthKeyState(ctx, a.AuthKeyID)
}
return true, nil
}
func (r *Router) onAuthCheckPassword(ctx context.Context, password tg.InputCheckPasswordSRPClass) (tg.AuthAuthorizationClass, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
if r.deps.Account == nil {
return nil, passwordHashInvalidErr()
}
if err := r.deps.Account.CheckPassword(ctx, userID, domainPasswordCheck(password)); err != nil {
return nil, passwordErr(err)
}
u, err := r.deps.Users.Self(ctx, userID)
if err != nil {
return nil, internalErr()
}
return &tg.AuthAuthorization{User: r.tgSelfUser(u)}, nil
}
func (r *Router) onAuthRequestPasswordRecovery(ctx context.Context) (*tg.AuthPasswordRecovery, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
pattern, err := r.deps.Account.RequestPasswordRecovery(ctx, userID)
if err != nil {
return nil, passwordErr(err)
}
return &tg.AuthPasswordRecovery{EmailPattern: pattern}, nil
}
func (r *Router) onAuthRecoverPassword(ctx context.Context, req *tg.AuthRecoverPasswordRequest) (tg.AuthAuthorizationClass, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
var input *domain.PasswordInputSettings
if settings, ok := req.GetNewSettings(); ok {
converted, err := domainPasswordInputSettings(settings)
if err != nil {
return nil, err
}
input = &converted
}
if err := r.deps.Account.RecoverPassword(ctx, userID, req.Code, input); err != nil {
return nil, passwordErr(err)
}
u, err := r.deps.Users.Self(ctx, userID)
if err != nil {
return nil, internalErr()
}
return &tg.AuthAuthorization{User: r.tgSelfUser(u)}, nil
}
func (r *Router) onAuthCheckRecoveryPassword(ctx context.Context, code string) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
if err := r.deps.Account.CheckRecoveryPassword(ctx, userID, code); err != nil {
return false, passwordErr(err)
}
return true, nil
}
// onAuthSignUp 处理 auth.signUp创建用户并绑定授权。
func (r *Router) onAuthSignUp(ctx context.Context, req *tg.AuthSignUpRequest) (tg.AuthAuthorizationClass, error) {
u, loginMessage, err := r.deps.Auth.SignUp(ctx, r.authzFromCtx(ctx), req.PhoneNumber, req.PhoneCodeHash, req.FirstName, req.LastName)

View file

@ -1875,17 +1875,95 @@ func tgPassword(settings domain.PasswordSettings) *tg.AccountPassword {
if len(settings.SecureRandom) == 0 {
settings.SecureRandom = []byte("telesrv-tdesktop-dev-secure-rand")
}
return &tg.AccountPassword{
out := &tg.AccountPassword{
HasRecovery: settings.HasRecovery,
HasSecureValues: settings.HasSecureValues,
HasPassword: settings.HasPassword,
Hint: settings.Hint,
EmailUnconfirmedPattern: settings.EmailUnconfirmedPattern,
NewAlgo: &tg.PasswordKdfAlgoUnknown{},
NewSecureAlgo: &tg.SecurePasswordKdfAlgoUnknown{},
NewAlgo: tgPasswordAlgo(settings.NewAlgo),
NewSecureAlgo: tgSecurePasswordAlgo(settings.NewSecureAlgo),
SecureRandom: settings.SecureRandom,
LoginEmailPattern: settings.LoginEmailPattern,
}
if settings.HasPassword && settings.CurrentAlgo != nil {
out.CurrentAlgo = tgPasswordAlgo(*settings.CurrentAlgo)
out.SRPB = append([]byte(nil), settings.SRPB...)
out.SRPID = settings.SRPID
}
if settings.PendingResetDate != 0 {
out.PendingResetDate = settings.PendingResetDate
}
return out
}
func tgPasswordAlgo(algo domain.PasswordKDFAlgo) tg.PasswordKdfAlgoClass {
if len(algo.P) == 0 || algo.G == 0 {
return &tg.PasswordKdfAlgoUnknown{}
}
return &tg.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow{
Salt1: append([]byte(nil), algo.Salt1...),
Salt2: append([]byte(nil), algo.Salt2...),
G: algo.G,
P: append([]byte(nil), algo.P...),
}
}
func domainPasswordAlgo(in tg.PasswordKdfAlgoClass) (*domain.PasswordKDFAlgo, bool) {
if algo, ok := in.(*tg.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow); ok {
return &domain.PasswordKDFAlgo{
Salt1: append([]byte(nil), algo.Salt1...),
Salt2: append([]byte(nil), algo.Salt2...),
G: algo.G,
P: append([]byte(nil), algo.P...),
}, true
}
return nil, false
}
func tgSecurePasswordAlgo(algo domain.SecurePasswordKDFAlgo) tg.SecurePasswordKdfAlgoClass {
if algo.Kind == "pbkdf2_hmac_sha512_iter100000" {
return &tg.SecurePasswordKdfAlgoPBKDF2HMACSHA512iter100000{Salt: append([]byte(nil), algo.Salt...)}
}
return &tg.SecurePasswordKdfAlgoUnknown{}
}
func domainPasswordCheck(in tg.InputCheckPasswordSRPClass) domain.PasswordCheck {
if srp, ok := in.(*tg.InputCheckPasswordSRP); ok {
return domain.PasswordCheck{
SRPID: srp.SRPID,
A: append([]byte(nil), srp.A...),
M1: append([]byte(nil), srp.M1...),
}
}
return domain.PasswordCheck{Empty: true}
}
func domainPasswordInputSettings(in tg.AccountPasswordInputSettings) (domain.PasswordInputSettings, error) {
out := domain.PasswordInputSettings{}
if algo, ok := in.GetNewAlgo(); ok {
domainAlgo, ok := domainPasswordAlgo(algo)
if !ok {
return out, passwordHashInvalidErr()
}
out.NewAlgo = domainAlgo
out.NewPasswordHash = append([]byte(nil), in.NewPasswordHash...)
out.Hint = in.Hint
out.HasHint = true
}
if email, ok := in.GetEmail(); ok {
out.Email = email
out.HasEmail = true
}
return out, nil
}
func tgPasswordSettings(settings domain.PrivatePasswordSettings) *tg.AccountPasswordSettings {
out := &tg.AccountPasswordSettings{}
if settings.Email != "" {
out.Email = settings.Email
}
return out
}
func tgCountriesList(list domain.CountriesList) tg.HelpCountriesListClass {

View file

@ -20,9 +20,14 @@ type AuthService interface {
ResolveAuthKey(ctx context.Context, authKeyID [8]byte) ([8]byte, bool, error)
UserID(ctx context.Context, authKeyID [8]byte) (int64, bool, error)
SendCode(ctx context.Context, phone string) (string, error)
ResendCode(ctx context.Context, phone, phoneCodeHash string) (string, error)
CancelCode(ctx context.Context, phone, phoneCodeHash string) error
SignIn(ctx context.Context, a domain.Authorization, phone, phoneCodeHash, code string) (domain.User, domain.Message, bool, error)
SignUp(ctx context.Context, a domain.Authorization, phone, phoneCodeHash, firstName, lastName string) (domain.User, domain.Message, error)
LogOut(ctx context.Context, authKeyID [8]byte) error
ListAuthorizations(ctx context.Context, userID int64) ([]domain.Authorization, error)
ResetAuthorization(ctx context.Context, userID, hash int64) (domain.Authorization, bool, error)
ResetAuthorizations(ctx context.Context, userID int64, keepAuthKeyID [8]byte) ([]domain.Authorization, error)
}
// SessionBinder 抽象登录后 session 与 user 的在线绑定。
@ -99,6 +104,17 @@ type UserIdentityService interface {
// AccountService 抽象账号设置查询。
type AccountService interface {
GetPassword(ctx context.Context, userID int64) (domain.PasswordSettings, error)
GetPasswordSettings(ctx context.Context, userID int64, check domain.PasswordCheck) (domain.PrivatePasswordSettings, error)
UpdatePasswordSettings(ctx context.Context, userID int64, check domain.PasswordCheck, input domain.PasswordInputSettings) error
CheckPassword(ctx context.Context, userID int64, check domain.PasswordCheck) error
RequestPasswordRecovery(ctx context.Context, userID int64) (string, error)
CheckRecoveryPassword(ctx context.Context, userID int64, code string) error
RecoverPassword(ctx context.Context, userID int64, code string, input *domain.PasswordInputSettings) error
ConfirmPasswordEmail(ctx context.Context, userID int64, code string) error
ResendPasswordEmail(ctx context.Context, userID int64) error
CancelPasswordEmail(ctx context.Context, userID int64) error
ResetPassword(ctx context.Context, userID int64) (domain.PasswordResetResult, error)
DeclinePasswordReset(ctx context.Context, userID int64) error
}
// PrivacyService owns account privacy rule storage/evaluation.

View file

@ -118,6 +118,10 @@ func usernameNotModifiedErr() error { return tgerr.New(400, "USERNAME_NOT_MODIFI
func phoneNotOccupiedErr() error { return tgerr.New(400, "PHONE_NOT_OCCUPIED") }
func dcIDInvalidErr() error { return tgerr.New(400, "DC_ID_INVALID") }
func authTokenInvalidErr() error { return tgerr.New(400, "AUTH_TOKEN_INVALID") }
func userIDInvalidErr() error { return tgerr.New(400, "USER_ID_INVALID") }
func usersTooFewErr() error { return tgerr.New(400, "USERS_TOO_FEW") }
@ -152,6 +156,16 @@ func messageDeleteForbiddenErr() error { return tgerr.New(403, "DELETE_MESSAGES_
func messageNotReadYetErr() error { return tgerr.New(400, "MESSAGE_NOT_READ_YET") }
func sessionPasswordNeededErr() error { return tgerr.New(401, "SESSION_PASSWORD_NEEDED") }
func passwordHashInvalidErr() error { return tgerr.New(400, "PASSWORD_HASH_INVALID") }
func srpIDInvalidErr() error { return tgerr.New(400, "SRP_ID_INVALID") }
func srpPasswordChangedErr() error { return tgerr.New(400, "SRP_PASSWORD_CHANGED") }
func newSettingsInvalidErr() error { return tgerr.New(400, "NEW_SETTINGS_INVALID") }
func newSaltInvalidErr() error { return tgerr.New(400, "NEW_SALT_INVALID") }
func emailInvalidErr() error { return tgerr.New(400, "EMAIL_INVALID") }
func emailCodeInvalidErr() error { return tgerr.New(400, "CODE_INVALID") }
func passwordRecoveryNAErr() error { return tgerr.New(400, "PASSWORD_RECOVERY_NA") }
func replyMessageIDInvalidErr() error { return tgerr.New(400, "REPLY_MESSAGE_ID_INVALID") }
func chatForwardsRestrictedErr() error { return tgerr.New(400, "CHAT_FORWARDS_RESTRICTED") }
@ -188,6 +202,31 @@ func signInErr(err error) error {
return tgerr.New(400, "PHONE_CODE_EXPIRED")
case errors.Is(err, domain.ErrFirstNameInvalid):
return firstNameInvalidErr()
case errors.Is(err, domain.ErrSessionPasswordNeeded):
return sessionPasswordNeededErr()
default:
return internalErr()
}
}
func passwordErr(err error) error {
switch {
case errors.Is(err, domain.ErrPasswordHashInvalid):
return passwordHashInvalidErr()
case errors.Is(err, domain.ErrSRPIDInvalid):
return srpIDInvalidErr()
case errors.Is(err, domain.ErrSRPPasswordChanged):
return srpPasswordChangedErr()
case errors.Is(err, domain.ErrNewSettingsInvalid):
return newSettingsInvalidErr()
case errors.Is(err, domain.ErrNewSaltInvalid):
return newSaltInvalidErr()
case errors.Is(err, domain.ErrEmailInvalid):
return emailInvalidErr()
case errors.Is(err, domain.ErrEmailCodeInvalid):
return emailCodeInvalidErr()
case errors.Is(err, domain.ErrPasswordRecoveryNA):
return passwordRecoveryNAErr()
default:
return internalErr()
}

View file

@ -403,11 +403,19 @@ func TestTDesktopStartupRPCsEncode(t *testing.T) {
{name: "help.getPremiumPromo", req: &tg.HelpGetPremiumPromoRequest{}},
{name: "account.getPassword", req: &tg.AccountGetPasswordRequest{}},
{name: "account.getNotifySettings", req: &tg.AccountGetNotifySettingsRequest{Peer: &tg.InputNotifyUsers{}}},
{name: "account.resetNotifySettings", req: &tg.AccountResetNotifySettingsRequest{}},
{name: "account.getPrivacy", req: &tg.AccountGetPrivacyRequest{Key: &tg.InputPrivacyKeyStatusTimestamp{}}},
{name: "account.getAuthorizations", req: &tg.AccountGetAuthorizationsRequest{}},
{name: "account.getWebAuthorizations", req: &tg.AccountGetWebAuthorizationsRequest{}},
{name: "account.getNotifyExceptions", req: &tg.AccountGetNotifyExceptionsRequest{}},
{name: "account.getDefaultEmojiStatuses", req: &tg.AccountGetDefaultEmojiStatusesRequest{}},
{name: "account.getRecentEmojiStatuses", req: &tg.AccountGetRecentEmojiStatusesRequest{}},
{name: "account.getCollectibleEmojiStatuses", req: &tg.AccountGetCollectibleEmojiStatusesRequest{}},
{name: "account.getDefaultProfilePhotoEmojis", req: &tg.AccountGetDefaultProfilePhotoEmojisRequest{}},
{name: "account.getDefaultGroupPhotoEmojis", req: &tg.AccountGetDefaultGroupPhotoEmojisRequest{}},
{name: "account.getDefaultBackgroundEmojis", req: &tg.AccountGetDefaultBackgroundEmojisRequest{}},
{name: "account.getChannelDefaultEmojiStatuses", req: &tg.AccountGetChannelDefaultEmojiStatusesRequest{}},
{name: "account.getChannelRestrictedStatusEmojis", req: &tg.AccountGetChannelRestrictedStatusEmojisRequest{}},
{name: "account.getConnectedBots", req: &tg.AccountGetConnectedBotsRequest{}},
{name: "account.getReactionsNotifySettings", req: &tg.AccountGetReactionsNotifySettingsRequest{}},
{name: "account.getContactSignUpNotification", req: &tg.AccountGetContactSignUpNotificationRequest{}},
@ -415,7 +423,9 @@ func TestTDesktopStartupRPCsEncode(t *testing.T) {
{name: "account.getContentSettings", req: &tg.AccountGetContentSettingsRequest{}},
{name: "account.getGlobalPrivacySettings", req: &tg.AccountGetGlobalPrivacySettingsRequest{}},
{name: "account.getPasskeys", req: &tg.AccountGetPasskeysRequest{}},
{name: "account.getAutoDownloadSettings", req: &tg.AccountGetAutoDownloadSettingsRequest{}},
{name: "account.getSavedMusicIds", req: &tg.AccountGetSavedMusicIDsRequest{}},
{name: "account.resetPassword", req: &tg.AccountResetPasswordRequest{}},
{name: "account.updateStatus", req: &tg.AccountUpdateStatusRequest{Offline: true}},
{name: "updates.getDifference", req: &tg.UpdatesGetDifferenceRequest{}},
{name: "users.getFullUser", req: &tg.UsersGetFullUserRequest{ID: &tg.InputUserSelf{}}},
@ -9401,6 +9411,14 @@ func (s *blockingUserAuthService) SendCode(context.Context, string) (string, err
return "", nil
}
func (s *blockingUserAuthService) ResendCode(context.Context, string, string) (string, error) {
return "", nil
}
func (s *blockingUserAuthService) CancelCode(context.Context, string, string) error {
return nil
}
func (s *blockingUserAuthService) SignIn(context.Context, domain.Authorization, string, string, string) (domain.User, domain.Message, bool, error) {
return domain.User{}, domain.Message{}, false, nil
}
@ -9413,6 +9431,18 @@ func (s *blockingUserAuthService) LogOut(context.Context, [8]byte) error {
return nil
}
func (s *blockingUserAuthService) ListAuthorizations(context.Context, int64) ([]domain.Authorization, error) {
return nil, nil
}
func (s *blockingUserAuthService) ResetAuthorization(context.Context, int64, int64) (domain.Authorization, bool, error) {
return domain.Authorization{}, false, nil
}
func (s *blockingUserAuthService) ResetAuthorizations(context.Context, int64, [8]byte) ([]domain.Authorization, error) {
return nil, nil
}
func (s *captureAuthService) BindTempAuthKey(context.Context, int64, domain.TempAuthKeyBinding) error {
return nil
}
@ -9431,6 +9461,14 @@ func (s *captureAuthService) SendCode(context.Context, string) (string, error) {
return "", nil
}
func (s *captureAuthService) ResendCode(context.Context, string, string) (string, error) {
return "", nil
}
func (s *captureAuthService) CancelCode(context.Context, string, string) error {
return nil
}
func (s *captureAuthService) SignIn(context.Context, domain.Authorization, string, string, string) (domain.User, domain.Message, bool, error) {
if s.signInUser.ID != 0 {
return s.signInUser, domain.Message{}, false, nil
@ -9447,6 +9485,18 @@ func (s *captureAuthService) LogOut(_ context.Context, authKeyID [8]byte) error
return nil
}
func (s *captureAuthService) ListAuthorizations(context.Context, int64) ([]domain.Authorization, error) {
return nil, nil
}
func (s *captureAuthService) ResetAuthorization(context.Context, int64, int64) (domain.Authorization, bool, error) {
return domain.Authorization{}, false, nil
}
func (s *captureAuthService) ResetAuthorizations(context.Context, int64, [8]byte) ([]domain.Authorization, error) {
return nil, nil
}
func (s staticUsersService) Self(context.Context, int64) (domain.User, error) {
return s.user, nil
}