github.com/jcarley/cli@v0.0.0-20180201210820-966d90434c30/lib/auth/signin.go (about) 1 package auth 2 3 import ( 4 "crypto" 5 "crypto/rand" 6 "crypto/sha256" 7 "crypto/x509" 8 "encoding/base64" 9 "encoding/json" 10 "encoding/pem" 11 "errors" 12 "fmt" 13 "io/ioutil" 14 15 "github.com/daticahealth/cli/config" 16 "github.com/daticahealth/cli/models" 17 ) 18 19 // Signin signs in a user and returns the representative user model. If an 20 // error occurs, nil is returned for the user and the error field is populated. 21 func (a *SAuth) Signin() (*models.User, error) { 22 // if we're already signed in with a valid session, don't sign in again 23 if user, err := a.Verify(); err == nil { 24 return user, nil 25 } 26 f := a.signInWithKey 27 if a.Settings.PrivateKeyPath == "" { 28 f = a.signInWithCredentials 29 } 30 signinResp, err := f() 31 if err != nil { 32 return nil, err 33 } 34 35 var user *models.User 36 37 if signinResp.MFAID != "" { 38 user, err = a.mfaSignin(signinResp.MFAID, signinResp.MFAPreferredMode) 39 if err != nil { 40 return nil, err 41 } 42 } else { 43 user = signinResp.toUser() 44 } 45 46 a.Settings.UsersID = user.UsersID 47 a.Settings.Email = user.Email 48 a.Settings.SessionToken = user.SessionToken 49 50 config.SaveSettings(a.Settings) 51 52 return user, nil 53 } 54 55 type signinResponse struct { 56 ID string `json:"id"` 57 Name string `json:"name"` 58 Email string `json:"email"` 59 SessionToken string `json:"sessionToken"` 60 MFAID string `json:"mfaID"` 61 MFAPreferredMode string `json:"mfaPreferredType"` 62 } 63 64 func (sr *signinResponse) toUser() *models.User { 65 return &models.User{ 66 UsersID: sr.ID, 67 Email: sr.Email, 68 SessionToken: sr.SessionToken, 69 } 70 } 71 72 func (a *SAuth) signInWithCredentials() (*signinResponse, error) { 73 login := models.Login{ 74 Identifier: a.Settings.Email, 75 Password: a.Settings.Password, 76 } 77 if a.Settings.Email == "" || a.Settings.Password == "" { 78 email, password, err := a.Prompts.EmailPassword(a.Settings.Email, a.Settings.Password) 79 if err != nil { 80 return nil, err 81 } 82 login = models.Login{ 83 Identifier: email, 84 Password: password, 85 } 86 } 87 88 b, err := json.Marshal(login) 89 if err != nil { 90 return nil, err 91 } 92 headers := a.Settings.HTTPManager.GetHeaders(a.Settings.SessionToken, a.Settings.Version, a.Settings.Pod, a.Settings.UsersID) 93 resp, statusCode, err := a.Settings.HTTPManager.Post(b, fmt.Sprintf("%s%s/auth/signin", a.Settings.AuthHost, a.Settings.AuthHostVersion), headers) 94 if err != nil { 95 return nil, err 96 } 97 signinResp := &signinResponse{} 98 return signinResp, a.Settings.HTTPManager.ConvertResp(resp, statusCode, signinResp) 99 } 100 101 func (a *SAuth) signInWithKey() (*signinResponse, error) { 102 body := struct { 103 PublicKey string `json:"publicKey"` 104 Signature string `json:"signature"` 105 }{} 106 107 bytes, err := ioutil.ReadFile(a.Settings.PrivateKeyPath) 108 if err != nil { 109 return nil, err 110 } 111 block, _ := pem.Decode(bytes) 112 if block == nil { 113 return nil, errors.New("Private key is not PEM-encoded") 114 } 115 bytes = block.Bytes 116 if x509.IsEncryptedPEMBlock(block) { 117 passphrase := a.Prompts.KeyPassphrase(a.Settings.PrivateKeyPath) 118 bytes, err = x509.DecryptPEMBlock(block, []byte(passphrase)) 119 if err != nil { 120 return nil, err 121 } 122 } 123 privateKey, err := x509.ParsePKCS1PrivateKey(bytes) 124 if err != nil { 125 return nil, err 126 } 127 publicKey, err := ioutil.ReadFile(a.Settings.PrivateKeyPath + ".pub") 128 if err != nil { 129 return nil, err 130 } 131 body.PublicKey = string(publicKey) 132 133 headers := a.Settings.HTTPManager.GetHeaders(a.Settings.SessionToken, a.Settings.Version, a.Settings.Pod, a.Settings.UsersID) 134 message := fmt.Sprintf("%s&%s", headers["X-Request-Nonce"][0], headers["X-Request-Timestamp"][0]) 135 hashedMessage := sha256.Sum256([]byte(message)) 136 signature, err := privateKey.Sign(rand.Reader, hashedMessage[:], crypto.SHA256) 137 if err != nil { 138 return nil, err 139 } 140 body.Signature = base64.StdEncoding.EncodeToString(signature) 141 142 b, err := json.Marshal(body) 143 if err != nil { 144 return nil, err 145 } 146 resp, statusCode, err := a.Settings.HTTPManager.Post(b, fmt.Sprintf("%s%s/auth/signin/key", a.Settings.AuthHost, a.Settings.AuthHostVersion), headers) 147 if err != nil { 148 return nil, err 149 } 150 signinResp := &signinResponse{} 151 return signinResp, a.Settings.HTTPManager.ConvertResp(resp, statusCode, signinResp) 152 } 153 154 func (a *SAuth) mfaSignin(mfaID string, preferredMode string) (*models.User, error) { 155 token := a.Prompts.OTP(preferredMode) 156 headers := a.Settings.HTTPManager.GetHeaders(a.Settings.SessionToken, a.Settings.Version, a.Settings.Pod, a.Settings.UsersID) 157 b, err := json.Marshal(struct { 158 OTP string `json:"otp"` 159 }{OTP: token}) 160 if err != nil { 161 return nil, err 162 } 163 resp, statusCode, err := a.Settings.HTTPManager.Post(b, fmt.Sprintf("%s%s/auth/signin/mfa/%s", a.Settings.AuthHost, a.Settings.AuthHostVersion, mfaID), headers) 164 user := &models.User{} 165 err = a.Settings.HTTPManager.ConvertResp(resp, statusCode, user) 166 if err != nil { 167 return nil, err 168 } 169 return user, err 170 } 171 172 // Signout signs out a user by their session token. 173 func (a *SAuth) Signout() error { 174 headers := a.Settings.HTTPManager.GetHeaders(a.Settings.SessionToken, a.Settings.Version, a.Settings.Pod, a.Settings.UsersID) 175 resp, statusCode, err := a.Settings.HTTPManager.Delete(nil, fmt.Sprintf("%s%s/auth/signout", a.Settings.AuthHost, a.Settings.AuthHostVersion), headers) 176 if err != nil { 177 return err 178 } 179 return a.Settings.HTTPManager.ConvertResp(resp, statusCode, nil) 180 } 181 182 // Verify verifies if a given session token is still valid or not. If it is 183 // valid, the returned error will be nil. 184 func (a *SAuth) Verify() (*models.User, error) { 185 headers := a.Settings.HTTPManager.GetHeaders(a.Settings.SessionToken, a.Settings.Version, a.Settings.Pod, a.Settings.UsersID) 186 resp, statusCode, err := a.Settings.HTTPManager.Get(nil, fmt.Sprintf("%s%s/auth/verify", a.Settings.AuthHost, a.Settings.AuthHostVersion), headers) 187 if err != nil { 188 return nil, err 189 } 190 var user models.User 191 err = a.Settings.HTTPManager.ConvertResp(resp, statusCode, &user) 192 if err != nil { 193 return nil, err 194 } 195 a.Settings.UsersID = user.UsersID 196 user.SessionToken = a.Settings.SessionToken 197 return &user, nil 198 }