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.
180 lines
4.8 KiB
180 lines
4.8 KiB
3 years ago
|
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
|
||
|
}
|