mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-14 10:50:45 +01:00
da75a76d41
For better auditing this PR adds a dashboard login event to the management service. For that the user object was extended with a field for last login that is not actively saved to the database but kept in memory until next write. The information about the last login can be extracted from the JWT claims nb_last_login. This timestamp will be stored and compared on each API request. If the value changes we generate an event to inform about a login.
125 lines
3.5 KiB
Go
125 lines
3.5 KiB
Go
package jwtclaims
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt"
|
|
)
|
|
|
|
const (
|
|
// TokenUserProperty key for the user property in the request context
|
|
TokenUserProperty = "user"
|
|
// AccountIDSuffix suffix for the account id claim
|
|
AccountIDSuffix = "wt_account_id"
|
|
// DomainIDSuffix suffix for the domain id claim
|
|
DomainIDSuffix = "wt_account_domain"
|
|
// DomainCategorySuffix suffix for the domain category claim
|
|
DomainCategorySuffix = "wt_account_domain_category"
|
|
// UserIDClaim claim for the user id
|
|
UserIDClaim = "sub"
|
|
// LastLoginSuffix claim for the last login
|
|
LastLoginSuffix = "nb_last_login"
|
|
)
|
|
|
|
// ExtractClaims Extract function type
|
|
type ExtractClaims func(r *http.Request) AuthorizationClaims
|
|
|
|
// ClaimsExtractor struct that holds the extract function
|
|
type ClaimsExtractor struct {
|
|
authAudience string
|
|
userIDClaim string
|
|
|
|
FromRequestContext ExtractClaims
|
|
}
|
|
|
|
// ClaimsExtractorOption is a function that configures the ClaimsExtractor
|
|
type ClaimsExtractorOption func(*ClaimsExtractor)
|
|
|
|
// WithAudience sets the audience for the extractor
|
|
func WithAudience(audience string) ClaimsExtractorOption {
|
|
return func(c *ClaimsExtractor) {
|
|
c.authAudience = audience
|
|
}
|
|
}
|
|
|
|
// WithUserIDClaim sets the user id claim for the extractor
|
|
func WithUserIDClaim(userIDClaim string) ClaimsExtractorOption {
|
|
return func(c *ClaimsExtractor) {
|
|
c.userIDClaim = userIDClaim
|
|
}
|
|
}
|
|
|
|
// WithFromRequestContext sets the function that extracts claims from the request context
|
|
func WithFromRequestContext(ec ExtractClaims) ClaimsExtractorOption {
|
|
return func(c *ClaimsExtractor) {
|
|
c.FromRequestContext = ec
|
|
}
|
|
}
|
|
|
|
// NewClaimsExtractor returns an extractor, and if provided with a function with ExtractClaims signature,
|
|
// then it will use that logic. Uses ExtractClaimsFromRequestContext by default
|
|
func NewClaimsExtractor(options ...ClaimsExtractorOption) *ClaimsExtractor {
|
|
ce := &ClaimsExtractor{}
|
|
for _, option := range options {
|
|
option(ce)
|
|
}
|
|
if ce.FromRequestContext == nil {
|
|
ce.FromRequestContext = ce.fromRequestContext
|
|
}
|
|
if ce.userIDClaim == "" {
|
|
ce.userIDClaim = UserIDClaim
|
|
}
|
|
return ce
|
|
}
|
|
|
|
// FromToken extracts claims from the token (after auth)
|
|
func (c *ClaimsExtractor) FromToken(token *jwt.Token) AuthorizationClaims {
|
|
claims := token.Claims.(jwt.MapClaims)
|
|
jwtClaims := AuthorizationClaims{
|
|
Raw: claims,
|
|
}
|
|
userID, ok := claims[c.userIDClaim].(string)
|
|
if !ok {
|
|
return jwtClaims
|
|
}
|
|
jwtClaims.UserId = userID
|
|
accountIDClaim, ok := claims[c.authAudience+AccountIDSuffix]
|
|
if ok {
|
|
jwtClaims.AccountId = accountIDClaim.(string)
|
|
}
|
|
domainClaim, ok := claims[c.authAudience+DomainIDSuffix]
|
|
if ok {
|
|
jwtClaims.Domain = domainClaim.(string)
|
|
}
|
|
domainCategoryClaim, ok := claims[c.authAudience+DomainCategorySuffix]
|
|
if ok {
|
|
jwtClaims.DomainCategory = domainCategoryClaim.(string)
|
|
}
|
|
LastLoginClaimString, ok := claims[c.authAudience+LastLoginSuffix]
|
|
if ok {
|
|
jwtClaims.LastLogin = parseTime(LastLoginClaimString.(string))
|
|
}
|
|
return jwtClaims
|
|
}
|
|
|
|
func parseTime(timeString string) time.Time {
|
|
if timeString == "" {
|
|
return time.Time{}
|
|
}
|
|
parsedTime, err := time.Parse(time.RFC3339, timeString)
|
|
if err != nil {
|
|
return time.Time{}
|
|
}
|
|
return parsedTime
|
|
}
|
|
|
|
// fromRequestContext extracts claims from the request context previously filled by the JWT token (after auth)
|
|
func (c *ClaimsExtractor) fromRequestContext(r *http.Request) AuthorizationClaims {
|
|
if r.Context().Value(TokenUserProperty) == nil {
|
|
return AuthorizationClaims{}
|
|
}
|
|
token := r.Context().Value(TokenUserProperty).(*jwt.Token)
|
|
return c.FromToken(token)
|
|
}
|