auth: complete account authorization flows
(cherry picked from commit 04f4527df32ad5c35720cccc41d27fe51549612f)
This commit is contained in:
parent
af41d18478
commit
6dc42942c8
21 changed files with 1780 additions and 50 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue