Files
coder/coderd/oauth2provider/secrets.go
T
Steven Masley 13ca9ead3a chore!: ensure consistent secret token generation and hashing (#20388)
This PR uses the same sha256 hashing technique as we use for APIKeys. So
now all randomly generated secrets will be hashed with sha256 for
consistency.

This is a breaking change for the oauth tokens. Since oauth is only
allowed for dev builds and experimental, this is ok.
2025-10-23 15:38:49 -05:00

86 lines
2.4 KiB
Go

package oauth2provider
import (
"fmt"
"strings"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/apikey"
"github.com/coder/coder/v2/cryptorand"
)
const (
// SecretIdentifier is the prefix added to all generated secrets.
SecretIdentifier = "coder"
)
// Constants for OAuth2 secret generation
const (
secretLength = 40 // Length of the actual secret part
displaySecretLength = 6 // Length of visible part in UI (last 6 characters)
)
type HashedAppSecret struct {
AppSecret
// Hashed is the server stored hash(secret,salt,...). Used for verifying a
// secret.
Hashed []byte
}
type AppSecret struct {
// Formatted contains the secret. This value is owned by the client, not the
// server. It is formatted to include the prefix.
Formatted string
// Secret is the raw secret value. This value should only be known to the client.
Secret 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
}
// ParseFormattedSecret parses a formatted secret like "coder_<prefix>_<secret"
func ParseFormattedSecret(formatted string) (AppSecret, error) {
parts := strings.Split(formatted, "_")
if len(parts) != 3 {
return AppSecret{}, xerrors.Errorf("incorrect number of parts: %d", len(parts))
}
if parts[0] != SecretIdentifier {
return AppSecret{}, xerrors.Errorf("incorrect scheme: %s", parts[0])
}
return AppSecret{
Formatted: formatted,
Prefix: parts[1],
Secret: parts[2],
}, nil
}
// GenerateSecret generates a secret to be used as a client secret, refresh
// token, or authorization code.
func GenerateSecret() (HashedAppSecret, error) {
// 40 characters matches the length of GitHub's client secrets.
secret, hashedSecret, err := apikey.GenerateSecret(40)
if err != nil {
return HashedAppSecret{}, 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 HashedAppSecret{}, err
}
return HashedAppSecret{
AppSecret: AppSecret{
Formatted: fmt.Sprintf("%s_%s_%s", SecretIdentifier, prefix, secret),
Secret: secret,
Prefix: prefix,
},
Hashed: hashedSecret,
}, nil
}