You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

179 lines
4.8 KiB

package auth
import (
"context"
"fmt"
"time"
"github.com/go-faster/errors"
"github.com/gotd/td/internal/crypto"
"github.com/gotd/td/internal/crypto/srp"
"github.com/gotd/td/tg"
)
// PasswordHash computes password hash to log in.
//
// See https://core.telegram.org/api/srp#checking-the-password-with-srp.
func PasswordHash(
password []byte,
srpID int64,
srpB, secureRandom []byte,
alg tg.PasswordKdfAlgoClass,
) (*tg.InputCheckPasswordSRP, error) {
s := srp.NewSRP(crypto.DefaultRand())
algo, ok := alg.(*tg.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow)
if !ok {
return nil, errors.Errorf("unsupported algo: %T", alg)
}
a, err := s.Hash(password, srpB, secureRandom, srp.Input(*algo))
if err != nil {
return nil, errors.Wrap(err, "create SRP answer")
}
return &tg.InputCheckPasswordSRP{
SRPID: srpID,
A: a.A,
M1: a.M1,
}, nil
}
// NewPasswordHash computes new password hash to update password.
//
// Notice that NewPasswordHash mutates given alg.
//
// See https://core.telegram.org/api/srp#setting-a-new-2fa-password.
func NewPasswordHash(
password []byte,
algo *tg.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow,
) (hash []byte, _ error) {
s := srp.NewSRP(crypto.DefaultRand())
hash, newSalt, err := s.NewHash(password, srp.Input(*algo))
if err != nil {
return nil, errors.Wrap(err, "create SRP answer")
}
algo.Salt1 = newSalt
return hash, nil
}
var (
emptyPassword tg.InputCheckPasswordSRPClass = &tg.InputCheckPasswordEmpty{}
)
// UpdatePasswordOptions is options structure for UpdatePassword.
type UpdatePasswordOptions struct {
// Hint is new password hint.
Hint string
// Password is password callback.
//
// If password was requested and Password is nil, ErrPasswordNotProvided error will be returned.
Password func(ctx context.Context) (string, error)
}
// UpdatePassword sets new cloud password for this account.
//
// See https://core.telegram.org/api/srp#setting-a-new-2fa-password.
func (c *Client) UpdatePassword(
ctx context.Context,
newPassword string,
opts UpdatePasswordOptions,
) error {
p, err := c.api.AccountGetPassword(ctx)
if err != nil {
return errors.Wrap(err, "get SRP parameters")
}
algo, ok := p.NewAlgo.(*tg.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow)
if !ok {
return errors.Errorf("unsupported algo: %T", p.NewAlgo)
}
newHash, err := NewPasswordHash([]byte(newPassword), algo)
if err != nil {
return errors.Wrap(err, "compute new password hash")
}
var old = emptyPassword
if p.HasPassword {
if opts.Password == nil {
return ErrPasswordNotProvided
}
oldPassword, err := opts.Password(ctx)
if err != nil {
return errors.Wrap(err, "get password")
}
hash, err := PasswordHash([]byte(oldPassword), p.SRPID, p.SRPB, p.SecureRandom, p.CurrentAlgo)
if err != nil {
return errors.Wrap(err, "compute old password hash")
}
old = hash
}
if _, err := c.api.AccountUpdatePasswordSettings(ctx, &tg.AccountUpdatePasswordSettingsRequest{
Password: old,
NewSettings: tg.AccountPasswordInputSettings{
NewAlgo: algo,
NewPasswordHash: newHash,
Hint: opts.Hint,
},
}); err != nil {
return errors.Wrap(err, "update password")
}
return nil
}
// ResetFailedWaitError reports that you recently requested a password reset that was cancel and need to wait until the
// specified date before requesting another reset.
type ResetFailedWaitError struct {
Result tg.AccountResetPasswordFailedWait
}
// Until returns time required to wait.
func (r ResetFailedWaitError) Until() time.Duration {
retryDate := time.Unix(int64(r.Result.RetryDate), 0)
return time.Until(retryDate)
}
// Error implements error.
func (r *ResetFailedWaitError) Error() string {
return fmt.Sprintf("wait to reset password (%s)", r.Until())
}
// ResetPassword resets cloud password and returns time to wait until reset be performed.
// If time is zero, password was successfully reset.
//
// May return ResetFailedWaitError.
//
// See https://core.telegram.org/api/srp#password-reset.
func (c *Client) ResetPassword(ctx context.Context) (time.Time, error) {
r, err := c.api.AccountResetPassword(ctx)
if err != nil {
return time.Time{}, errors.Wrap(err, "reset password")
}
switch v := r.(type) {
case *tg.AccountResetPasswordFailedWait:
return time.Time{}, &ResetFailedWaitError{Result: *v}
case *tg.AccountResetPasswordRequestedWait:
return time.Unix(int64(v.UntilDate), 0), nil
case *tg.AccountResetPasswordOk:
return time.Time{}, nil
default:
return time.Time{}, errors.Errorf("unexpected type %T", v)
}
}
// CancelPasswordReset cancels password reset.
//
// See https://core.telegram.org/api/srp#password-reset.
func (c *Client) CancelPasswordReset(ctx context.Context) error {
if _, err := c.api.AccountDeclinePasswordReset(ctx); err != nil {
return errors.Wrap(err, "cancel password reset")
}
return nil
}