mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
4bd7c7b7e0
Adds RFC 7009 token revocation endpoint
91 lines
2.5 KiB
Go
91 lines
2.5 KiB
Go
package oauth2provider
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/v2/coderd/userpassword"
|
|
"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 string
|
|
}
|
|
|
|
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, err := cryptorand.String(secretLength)
|
|
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
|
|
}
|
|
|
|
hashed, err := userpassword.Hash(secret)
|
|
if err != nil {
|
|
return HashedAppSecret{}, err
|
|
}
|
|
|
|
return HashedAppSecret{
|
|
AppSecret: AppSecret{
|
|
Formatted: fmt.Sprintf("%s_%s_%s", SecretIdentifier, prefix, secret),
|
|
Secret: secret,
|
|
Prefix: prefix,
|
|
},
|
|
Hashed: hashed,
|
|
}, nil
|
|
}
|