diff --git a/conventional.yaml b/conventional.yaml index 87215dd6..d27ac698 100644 --- a/conventional.yaml +++ b/conventional.yaml @@ -164,9 +164,13 @@ server: # these services can integrate with the ircd using JSON Web Tokens (https://jwt.io) # sometimes referred to with 'EXTJWT' jwt-services: - # # service name -> secret string the service uses to verify our tokens - # call-host: call-hosting-secret-token - # image-host: image-hosting-secret-token + # # service name + # call-host: + # # custom expiry length, default is 30s + # expiry-in-seconds: 45 + + # # secret string to verify the generated tokens + # secret: call-hosting-secret-token # allow use of the RESUME extension over plaintext connections: # do not enable this unless the ircd is only accessible over internal networks diff --git a/default.yaml b/default.yaml index b8fa06ae..611be84b 100644 --- a/default.yaml +++ b/default.yaml @@ -190,9 +190,13 @@ server: # these services can integrate with the ircd using JSON Web Tokens (https://jwt.io) # sometimes referred to with 'EXTJWT' jwt-services: - # # service name -> secret string the service uses to verify our tokens - # call-host: call-hosting-secret-token - # image-host: image-hosting-secret-token + # # service name + # call-host: + # # custom expiry length, default is 30s + # expiry-in-seconds: 45 + + # # secret string to verify the generated tokens + # secret: call-hosting-secret-token # allow use of the RESUME extension over plaintext connections: # do not enable this unless the ircd is only accessible over internal networks diff --git a/irc/config.go b/irc/config.go index 0e4e906e..cc81792f 100644 --- a/irc/config.go +++ b/irc/config.go @@ -471,6 +471,11 @@ type TorListenersConfig struct { MaxConnectionsPerDuration int `yaml:"max-connections-per-duration"` } +type JwtServiceConfig struct { + ExpiryInSeconds int64 `yaml:"expiry-in-seconds"` + Secret string +} + // Config defines the overall configuration. type Config struct { Network struct { @@ -502,9 +507,9 @@ type Config struct { MOTDFormatting bool `yaml:"motd-formatting"` ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` proxyAllowedFromNets []net.IPNet - WebIRC []webircConfig `yaml:"webirc"` - JwtServices map[string]string `yaml:"jwt-services"` - MaxSendQString string `yaml:"max-sendq"` + WebIRC []webircConfig `yaml:"webirc"` + JwtServices map[string]JwtServiceConfig `yaml:"jwt-services"` + MaxSendQString string `yaml:"max-sendq"` MaxSendQBytes int AllowPlaintextResume bool `yaml:"allow-plaintext-resume"` Compatibility struct { @@ -922,6 +927,13 @@ func LoadConfig(filename string) (config *Config, err error) { config.Server.capValues[caps.Multiline] = multilineCapValue } + // confirm jwt config + for name, info := range config.Server.JwtServices { + if info.Secret == "" { + return nil, fmt.Errorf("Could not parse jwt-services config, %s service has no secret set", name) + } + } + // handle legacy name 'bouncer' for 'multiclient' section: if config.Accounts.Bouncer != nil { config.Accounts.Multiclient = *config.Accounts.Bouncer diff --git a/irc/handlers.go b/irc/handlers.go index df7b1215..880f6d5f 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -922,7 +922,6 @@ func extjwtHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re } claims := jwt.MapClaims{ - "exp": time.Now().Unix() + expireInSeconds, "iss": server.name, "sub": client.Nick(), "account": accountName, @@ -945,8 +944,6 @@ func extjwtHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re } } - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - // we default to a secret of `*`. if you want a real secret setup a service in the config~ service := "*" secret := "*" @@ -954,14 +951,19 @@ func extjwtHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re service = strings.ToLower(msg.Params[1]) c := server.Config() - var exists bool - secret, exists = c.Server.JwtServices[service] + info, exists := c.Server.JwtServices[service] if !exists { rb.Add(nil, server.name, "FAIL", "EXTJWT", "NO_SUCH_SERVICE", client.t("No such service")) return false } + secret = info.Secret + if info.ExpiryInSeconds != 0 { + expireInSeconds = info.ExpiryInSeconds + } } + claims["exp"] = time.Now().Unix() + expireInSeconds + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString([]byte(secret)) if err == nil {