Files
authy-migration/objects.go
Alex Zorin 66bacad2aa Add fallbacks for token name if OriginalName unset
Will use Name if it is available, and otherwise will use UniqueID, which
is not descriptive but should always be available.

Should fix one of the complaints reported in #1.
2019-07-30 06:48:06 +10:00

197 lines
5.7 KiB
Go

package authy
import (
"crypto/rsa"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"strings"
)
// ViaMethod represents the methods available for new device registration
type ViaMethod string
const (
// ViaMethodPush to recieve an Authy app-based push notification
ViaMethodPush ViaMethod = "push"
// ViaMethodCall to receive a phone call
ViaMethodCall ViaMethod = "call"
// ViaMethodSMS to receive an SMS message
ViaMethodSMS ViaMethod = "sms"
)
// UserStatus is the response from:
// https://api.authy.com/json/users/{Country}-{Phone}/status
type UserStatus struct {
// Presumably, force device validation over HTTP rather than
// allowing a phone call or SMS. ("Over the top").
ForceOTT bool `json:"force_ott"`
// How many devices are registered to this Authy user
DevicesCount int `json:"devices_count"`
// Authy User ID
AuthyID uint64 `json:"authy_id"`
// Presumably some kind of opaque status string
Message string
// Whether this request was successful
Success bool
}
// IsActiveUser reports whether this is an an active, registered
// Authy user.
func (us UserStatus) IsActiveUser() bool {
return us.Success && us.AuthyID > 0 && us.Message == "active"
}
// StartDeviceRegistrationResponse is the response from:
// https://api.authy.com/json/users/{User_ID}/devices/registration/start
type StartDeviceRegistrationResponse struct {
// Message to display to the user upon receiving this response
Message string `json:"message"`
// The Request ID is used to poll the status of the device registration process
RequestID string `json:"request_id"`
// Purpose unclear
ApprovalPIN int `json:"approval_pin"`
// The ViaMethod
Provider string `json:"provider"`
// Whether the device registration request was accepted.
// This is distinct to the device registration being successful/complete.
Success bool `json:"success"`
}
// DeviceRegistrationStatus is the response from:
// https://api.authy.com/json/users/{User_ID}/devices/registration/{Request_ID}/status?api_key={API_Key}&locale=en-GB&signature=b54ff1b646b207ff2da50ecb9a0bc2c770a1357b04278c8dd402f835db2824f4
type DeviceRegistrationStatus struct {
// pending, accepted, rejected, ??
Status string `json:"status"`
// PIN is required to complete the device registration
PIN string `json:"pin"`
// Whether this status request was successful, distinct to whether the
// registration process is complete.
Success bool `json:"success"`
}
// CompleteDeviceRegistrationResponse is the response from:
// https://api.authy.com/json/users/16480/devices/registration/complete
type CompleteDeviceRegistrationResponse struct {
Device struct {
// The Device ID
ID uint64 `json:"id"`
// The Device Secret Seed (hex-encoded, 32-bytes). It is the TOTP
// secret that protects the authenticated endpoints.
SecretSeed string `json:"secret_seed"`
// Purpose not known.
APIKey string `json:"api_key"`
// Purpose not known, but probably whether this device is being
// re-installed.
Reinstall bool `json:"reinstall"`
} `json:"device"`
// The Authy User ID
AuthyID uint64 `json:"authy_id"`
}
// DevicePrivateKeyResponse is the response from
// https://api.authy.com/json/devices/{Device_ID}/rsa_key?api_key={API_Key}&&otp1={OTP_1}&otp2={OTP_2}&otp3={OTP_3}&device_id={DEVICE_ID}
type DevicePrivateKeyResponse struct {
Message string `json:"message"`
PrivateKey string `json:"private_key"`
Success bool `json:"success"`
}
// AsPrivateKey parses the PEM private key in PrivateKey
func (r DevicePrivateKeyResponse) AsPrivateKey() (*rsa.PrivateKey, error) {
if !r.Success || r.PrivateKey == "" {
return nil, errors.New("This response does not contain a device private key")
}
blk, _ := pem.Decode([]byte(r.PrivateKey))
pk, err := x509.ParsePKCS1PrivateKey(blk.Bytes)
if err != nil {
return nil, fmt.Errorf("Couldn't parse private key: %v", err)
}
return pk, nil
}
// AuthenticatorTokensResponse is the response from:
// https://api.authy.com/json/users/{User_ID}/authenticator_tokens?api_key={API_Key}&otp1={OTP_1}&otp2={OTP_2}&otp3={OTP_3}&device_id={Device_ID
type AuthenticatorTokensResponse struct {
// Display to user
Message string `json:"message"`
// Active encrypted authenticator token
AuthenticatorTokens []AuthenticatorToken `json:"authenticator_tokens"`
// Recently deleted, but not removed encrypted authenticator tokens
Deleted []AuthenticatorToken `json:"deleted"`
// Whether this request succeeded
Success bool `json:"success"`
}
// AuthenticatorToken is embedded in AuthenticatorTokensResponse
type AuthenticatorToken struct {
// In the Authy app, this is the visual icon type of this token
AccountType string `json:"account_type"`
// How many digits this TOTP token is
Digits int `json:"digits"`
// The encrypted TOTP seed
EncryptedSeed string `json:"encrypted_seed"`
// User-nominated name for the token
Name string `json:"name"`
// Purpose not known
OriginalName string `json:"original_name"`
// Purpose not known
PasswordTimestamp uint64 `json:"password_timestamp"`
// The salt used to encrypt the EncryptedSeed
Salt string `json:"salt"`
// The ID of this token
UniqueID string `json:"unique_id"`
}
// Decrypt returns the base32-encoded seed for this TOTP token, decrypted
// by passphrase.
func (t AuthenticatorToken) Decrypt(passphrase string) (string, error) {
secret, err := decryptToken(t.EncryptedSeed, t.Salt, passphrase)
if err != nil {
return "", err
}
buf, err := hex.DecodeString(secret)
if err != nil {
return "", err
}
return strings.ToUpper(string(buf)), nil
}
// Description returns OriginalName if not empty, otherwise Name,
// otherwise `Token-{UniqueID}`.
func (t AuthenticatorToken) Description() string {
if t.OriginalName != "" {
return t.OriginalName
}
if t.Name != "" {
return t.Name
}
return "Token-" + t.UniqueID
}