coder/enterprise/coderd/identityprovider/secrets.go

78 lines
2.2 KiB
Go

package identityprovider
import (
"fmt"
"strings"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/userpassword"
"github.com/coder/coder/v2/cryptorand"
)
type OAuth2ProviderAppSecret struct {
// Formatted contains the secret. This value is owned by the client, not the
// server. It is formatted to include the prefix.
Formatted string
// Prefix is the ID of this secret owned by the server. When a client uses a
// secret, this is the matching string to do a lookup on the hashed value. We
// cannot use the hashed value directly because the server does not store the
// salt.
Prefix string
// Hashed is the server stored hash(secret,salt,...). Used for verifying a
// secret.
Hashed string
}
// GenerateSecret generates a secret to be used as a client secret, refresh
// token, or authorization code.
func GenerateSecret() (OAuth2ProviderAppSecret, error) {
// 40 characters matches the length of GitHub's client secrets.
secret, err := cryptorand.String(40)
if err != nil {
return OAuth2ProviderAppSecret{}, err
}
// This ID is prefixed to the secret so it can be used to look up the secret
// when the user provides it, since we cannot just re-hash it to match as we
// will not have the salt.
prefix, err := cryptorand.String(10)
if err != nil {
return OAuth2ProviderAppSecret{}, err
}
hashed, err := userpassword.Hash(secret)
if err != nil {
return OAuth2ProviderAppSecret{}, err
}
return OAuth2ProviderAppSecret{
Formatted: fmt.Sprintf("coder_%s_%s", prefix, secret),
Prefix: prefix,
Hashed: hashed,
}, nil
}
type parsedSecret struct {
prefix string
secret string
}
// parseSecret extracts the ID and original secret from a secret.
func parseSecret(secret string) (parsedSecret, error) {
parts := strings.Split(secret, "_")
if len(parts) != 3 {
return parsedSecret{}, xerrors.Errorf("incorrect number of parts: %d", len(parts))
}
if parts[0] != "coder" {
return parsedSecret{}, xerrors.Errorf("incorrect scheme: %s", parts[0])
}
if len(parts[1]) == 0 {
return parsedSecret{}, xerrors.Errorf("prefix is invalid")
}
if len(parts[2]) == 0 {
return parsedSecret{}, xerrors.Errorf("invalid")
}
return parsedSecret{parts[1], parts[2]}, nil
}