Skip to content

Commit

Permalink
review fix: allow multiple token definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
slingamn committed Feb 6, 2024
1 parent 5de58c9 commit 2dbf871
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 38 deletions.
27 changes: 15 additions & 12 deletions default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -604,18 +604,21 @@ accounts:
enabled: false
# should we automatically create users on presentation of a valid token?
autocreate: true
algorithm: "hmac" # either 'hmac', 'rsa', or 'eddsa' (ed25519)
# hmac takes a symmetric key, rsa and eddsa take PEM-encoded public keys;
# either way, the key can be specified either as a YAML string:
key: "nANiZ1De4v6WnltCHN2H7Q"
# or as a path to the file containing the key:
#key-file: "jwt_pubkey.pem"
# list of JWT claim names to search for the user's account name (make sure the format
# is what you expect, especially if using "sub"):
account-claims: ["preferred_username"]
# if a claim is formatted as an email address, require it to have the following domain,
# and then strip off the domain and use the local-part as the account name:
#strip-domain: "example.com"
# any of these token definitions can be accepted, allowing for key rotation
tokens:
-
algorithm: "hmac" # either 'hmac', 'rsa', or 'eddsa' (ed25519)
# hmac takes a symmetric key, rsa and eddsa take PEM-encoded public keys;
# either way, the key can be specified either as a YAML string:
key: "nANiZ1De4v6WnltCHN2H7Q"
# or as a path to the file containing the key:
#key-file: "jwt_pubkey.pem"
# list of JWT claim names to search for the user's account name (make sure the format
# is what you expect, especially if using "sub"):
account-claims: ["preferred_username"]
# if a claim is formatted as an email address, require it to have the following domain,
# and then strip off the domain and use the local-part as the account name:
#strip-domain: "example.com"

# channel options
channels:
Expand Down
46 changes: 37 additions & 9 deletions irc/jwt/bearer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ var (

// JWTAuthConfig is the config for Ergo to accept JWTs via draft/bearer
type JWTAuthConfig struct {
Enabled bool `yaml:"enabled"`
Autocreate bool `yaml:"autocreate"`
Enabled bool `yaml:"enabled"`
Autocreate bool `yaml:"autocreate"`
Tokens []JWTAuthTokenConfig `yaml:"tokens"`
}

type JWTAuthTokenConfig struct {
Algorithm string `yaml:"algorithm"`
KeyString string `yaml:"key"`
KeyFile string `yaml:"key-file"`
Expand All @@ -35,6 +39,20 @@ func (j *JWTAuthConfig) Postprocess() error {
return nil
}

if len(j.Tokens) == 0 {
return fmt.Errorf("JWT authentication enabled, but no valid tokens defined")
}

for i := range j.Tokens {
if err := j.Tokens[i].Postprocess(); err != nil {
return err
}
}

return nil
}

func (j *JWTAuthTokenConfig) Postprocess() error {
keyBytes, err := j.keyBytes()
if err != nil {
return err
Expand Down Expand Up @@ -74,7 +92,21 @@ func (j *JWTAuthConfig) Postprocess() error {
return nil
}

func (j *JWTAuthConfig) keyBytes() (result []byte, err error) {
func (j *JWTAuthConfig) Validate(t string) (accountName string, err error) {
if !j.Enabled || len(j.Tokens) == 0 {
return "", ErrAuthDisabled
}

for i := range j.Tokens {
accountName, err = j.Tokens[i].Validate(t)
if err == nil {
return
}
}
return
}

func (j *JWTAuthTokenConfig) keyBytes() (result []byte, err error) {
if j.KeyFile != "" {
o, err := os.Open(j.KeyFile)
if err != nil {
Expand All @@ -89,15 +121,11 @@ func (j *JWTAuthConfig) keyBytes() (result []byte, err error) {
}

// implements jwt.Keyfunc
func (j *JWTAuthConfig) keyFunc(_ *jwt.Token) (interface{}, error) {
func (j *JWTAuthTokenConfig) keyFunc(_ *jwt.Token) (interface{}, error) {
return j.key, nil
}

func (j *JWTAuthConfig) Validate(t string) (accountName string, err error) {
if !j.Enabled {
return "", ErrAuthDisabled
}

func (j *JWTAuthTokenConfig) Validate(t string) (accountName string, err error) {
token, err := j.parser.Parse(t, j.keyFunc)
if err != nil {
return "", err
Expand Down
14 changes: 9 additions & 5 deletions irc/jwt/bearer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,15 @@ s/uzBKNwWf9UPTeIt+4JScg=

func TestJWTBearerAuth(t *testing.T) {
j := JWTAuthConfig{
Enabled: true,
Algorithm: "rsa",
KeyString: rsaTestPubKey,
AccountClaims: []string{"preferred_username", "email"},
StripDomain: "example.com",
Enabled: true,
Tokens: []JWTAuthTokenConfig{
{
Algorithm: "rsa",
KeyString: rsaTestPubKey,
AccountClaims: []string{"preferred_username", "email"},
StripDomain: "example.com",
},
},
}

if err := j.Postprocess(); err != nil {
Expand Down
27 changes: 15 additions & 12 deletions traditional.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -577,18 +577,21 @@ accounts:
enabled: false
# should we automatically create users on presentation of a valid token?
autocreate: true
algorithm: "hmac" # either 'hmac', 'rsa', or 'eddsa' (ed25519)
# hmac takes a symmetric key, rsa and eddsa take PEM-encoded public keys;
# either way, the key can be specified either as a YAML string:
key: "nANiZ1De4v6WnltCHN2H7Q"
# or as a path to the file containing the key:
#key-file: "jwt_pubkey.pem"
# list of JWT claim names to search for the user's account name (make sure the format
# is what you expect, especially if using "sub"):
account-claims: ["preferred_username"]
# if a claim is formatted as an email address, require it to have the following domain,
# and then strip off the domain and use the local-part as the account name:
#strip-domain: "example.com"
# any of these token definitions can be accepted, allowing for key rotation
tokens:
-
algorithm: "hmac" # either 'hmac', 'rsa', or 'eddsa' (ed25519)
# hmac takes a symmetric key, rsa and eddsa take PEM-encoded public keys;
# either way, the key can be specified either as a YAML string:
key: "nANiZ1De4v6WnltCHN2H7Q"
# or as a path to the file containing the key:
#key-file: "jwt_pubkey.pem"
# list of JWT claim names to search for the user's account name (make sure the format
# is what you expect, especially if using "sub"):
account-claims: ["preferred_username"]
# if a claim is formatted as an email address, require it to have the following domain,
# and then strip off the domain and use the local-part as the account name:
#strip-domain: "example.com"

# channel options
channels:
Expand Down

0 comments on commit 2dbf871

Please sign in to comment.