go-passbolt/api/auth.go
2021-11-18 12:20:52 +01:00

144 lines
4.4 KiB
Go

package api
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/gopenpgp/v2/helper"
)
// Login is used for login
type Login struct {
Auth *GPGAuth `json:"gpg_auth"`
}
// GPGAuth is used for login
type GPGAuth struct {
KeyID string `json:"keyid"`
Token string `json:"user_token_result,omitempty"`
}
// CheckSession Check to see if you have a Valid Session
func (c *Client) CheckSession(ctx context.Context) bool {
_, err := c.DoCustomRequest(ctx, "GET", "auth/is-authenticated.json", "v2", nil, nil)
return err == nil
}
// Login gets a Session and CSRF Token from Passbolt and Stores them in the Clients Cookie Jar
func (c *Client) Login(ctx context.Context) error {
c.csrfToken = http.Cookie{}
if c.userPrivateKey == "" {
return fmt.Errorf("Client has no Private Key")
}
privateKeyObj, err := crypto.NewKeyFromArmored(c.userPrivateKey)
if err != nil {
return fmt.Errorf("Parsing User Private Key: %w", err)
}
data := Login{&GPGAuth{KeyID: privateKeyObj.GetFingerprint()}}
res, _, err := c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "/auth/login.json", "v2", data, nil)
if err != nil && !strings.Contains(err.Error(), "Error API JSON Response Status: Message: The authentication failed.") {
return fmt.Errorf("Doing Stage 1 Request: %w", err)
}
encAuthToken := res.Header.Get("X-GPGAuth-User-Auth-Token")
if encAuthToken == "" {
return fmt.Errorf("Got Empty X-GPGAuth-User-Auth-Token Header")
}
c.log("Got Encrypted Auth Token: %v", encAuthToken)
encAuthToken, err = url.QueryUnescape(encAuthToken)
if err != nil {
return fmt.Errorf("Unescaping User Auth Token: %w", err)
}
encAuthToken = strings.ReplaceAll(encAuthToken, "\\ ", " ")
authToken, err := helper.DecryptMessageArmored(c.userPrivateKey, c.userPassword, encAuthToken)
if err != nil {
return fmt.Errorf("Decrypting User Auth Token: %w", err)
}
c.log("Decrypted Auth Token: %v", authToken)
err = checkAuthTokenFormat(authToken)
if err != nil {
return fmt.Errorf("Checking Auth Token Format: %w", err)
}
data.Auth.Token = string(authToken)
res, _, err = c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "/auth/login.json", "v2", data, nil)
if err != nil {
return fmt.Errorf("Doing Stage 2 Request: %w", err)
}
c.log("Got Cookies: %+v", res.Cookies())
for _, cookie := range res.Cookies() {
if cookie.Name == "passbolt_session" {
c.sessionToken = *cookie
// Session Cookie in older Passbolt Versions
} else if cookie.Name == "CAKEPHP" {
c.sessionToken = *cookie
// Session Cookie in Cloud version?
} else if cookie.Name == "PHPSESSID" {
c.sessionToken = *cookie
}
}
if c.sessionToken.Name == "" {
return fmt.Errorf("Cannot Find Session Cookie!")
}
// Because of MFA, the custom Request Function now Fetches the CSRF token, we still need the user for his public key
apiMsg, err := c.DoCustomRequest(ctx, "GET", "/users/me.json", "v2", nil, nil)
if err != nil {
return fmt.Errorf("Getting CSRF Token: %w", err)
}
// Get Users Own Public Key from Server
var user User
err = json.Unmarshal(apiMsg.Body, &user)
if err != nil {
return fmt.Errorf("Parsing User 'Me' JSON from API Request: %w", err)
}
// Validate that this Publickey that the Server gave us actually Matches our Privatekey
randomString := randStringBytesRmndr(50)
armor, err := helper.EncryptMessageArmored(user.GPGKey.ArmoredKey, randomString)
if err != nil {
return fmt.Errorf("Encryping PublicKey Validation Message: %w", err)
}
decrypted, err := helper.DecryptMessageArmored(c.userPrivateKey, c.userPassword, armor)
if err != nil {
return fmt.Errorf("Decrypting PublicKey Validation Message (you might be getting Hacked): %w", err)
}
if decrypted != randomString {
return fmt.Errorf("Decrypted PublicKey Validation Message does not Match Original (you might be getting Hacked): %w", err)
}
// Insert PublicKey into Client after checking it to Prevent ignored errors leading to proceeding with a potentially Malicious PublicKey
c.userPublicKey = user.GPGKey.ArmoredKey
c.userID = user.ID
return nil
}
// Logout closes the current Session on the Passbolt server
func (c *Client) Logout(ctx context.Context) error {
_, err := c.DoCustomRequest(ctx, "GET", "/auth/logout.json", "v2", nil, nil)
if err != nil {
return fmt.Errorf("Doing Logout Request: %w", err)
}
c.sessionToken = http.Cookie{}
c.csrfToken = http.Cookie{}
return nil
}