-
Notifications
You must be signed in to change notification settings - Fork 197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Basic EXTJWT support #948
Basic EXTJWT support #948
Changes from all commits
6d920fd
5a4f0be
60cc3d5
102e260
d9dbbc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ import ( | |
"strings" | ||
"time" | ||
|
||
"github.com/dgrijalva/jwt-go" | ||
"github.com/goshuirc/irc-go/ircfmt" | ||
"github.com/goshuirc/irc-go/ircmatch" | ||
"github.com/goshuirc/irc-go/ircmsg" | ||
|
@@ -889,6 +890,81 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res | |
return killClient | ||
} | ||
|
||
// EXTJWT <target> [service_name] | ||
func extjwtHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { | ||
expireInSeconds := int64(30) | ||
|
||
accountName := client.AccountName() | ||
if accountName == "*" { | ||
accountName = "" | ||
} | ||
|
||
claims := jwt.MapClaims{ | ||
"iss": server.name, | ||
"sub": client.Nick(), | ||
"account": accountName, | ||
"umodes": []string{}, | ||
} | ||
|
||
if msg.Params[0] != "*" { | ||
channel := server.channels.Get(msg.Params[0]) | ||
if channel == nil { | ||
rb.Add(nil, server.name, "FAIL", "EXTJWT", "NO_SUCH_CHANNEL", client.t("No such channel")) | ||
return false | ||
} | ||
|
||
claims["channel"] = channel.Name() | ||
claims["joined"] = 0 | ||
claims["cmodes"] = []string{} | ||
if channel.hasClient(client) { | ||
joinTime := channel.ClientJoinTime(client) | ||
if joinTime.IsZero() { | ||
// shouldn't happen, only in races | ||
rb.Add(nil, server.name, "FAIL", "EXTJWT", "UNKNOWN_ERROR", client.t("Channel join time is inconsistent, JWT not generated")) | ||
return false | ||
} | ||
claims["joined"] = joinTime.Unix() | ||
claims["cmodes"] = channel.ClientModeStrings(client) | ||
} | ||
} | ||
|
||
// we default to a secret of `*`. if you want a real secret setup a service in the config~ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's going on here exactly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you provide a service name (like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we sure signing with a secret of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we should not be mixing and matching with alg: none because of the very frequent issues that alg:none has caused in the past. I'm not sure whether using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not 100% clear on what's going on here but I have a really strong intuition that using a secret key of If I'm reading the spec correctly, we should be requiring the operator to specify a default key of some kind (which should be a genuine secret and not Should we ask for clarification on the spec PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The spec should clarify this, yeah. There should not be any way to have a custom default secret, any custom secret should be provided by using a service name. So we should probably remove this and only advertise JWT if there is at least one service configured. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We got the clarification we wanted:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm very confident that this is the correct answer. Clients do not parse/validate the JWT token themselves, they hand it off to a third party. That third party is assumed to hold the correct verification key. (The original use case for this is a closed ecosystem of an ircd and a Jitsi, controlled by the same operator.) A "secret key" that is a constant value written into a specification just doesn't make sense. |
||
service := "*" | ||
secret := "*" | ||
if 1 < len(msg.Params) { | ||
service = strings.ToLower(msg.Params[1]) | ||
|
||
c := server.Config() | ||
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 { | ||
maxTokenLength := 400 | ||
|
||
for maxTokenLength < len(tokenString) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. very elegant, i like this |
||
rb.Add(nil, server.name, "EXTJWT", msg.Params[0], service, "*", tokenString[:maxTokenLength]) | ||
tokenString = tokenString[maxTokenLength:] | ||
} | ||
rb.Add(nil, server.name, "EXTJWT", msg.Params[0], service, tokenString) | ||
} else { | ||
rb.Add(nil, server.name, "FAIL", "EXTJWT", "UNKNOWN_ERROR", client.t("Could not generate EXTJWT token")) | ||
} | ||
|
||
return false | ||
} | ||
|
||
// HELP [<query>] | ||
func helpHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { | ||
argument := strings.ToLower(strings.TrimSpace(strings.Join(msg.Params, " "))) | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh --- there must be some reason this is necessary, but I can't figure out what it is yet. I asked a question about this on the spec PR. It would nice if we didn't need this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I discussed this with prawn and it sounds like we can just send
1
as the join timestamp (indicating "the client is joined, but the join timestamp is unavailable"); we can probably wait a couple days and then remove this tracking.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once the spec is updated we can handle this however I guess, no worries
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spec got updated to allow
1
here.