diff --git a/auth/.env.example b/auth/.env.example index f1c14b4d..2ce80788 100644 --- a/auth/.env.example +++ b/auth/.env.example @@ -12,6 +12,8 @@ EXTRA_CLAIMS='{}' # json object with the claims to add to every jwt of the FIRST user (this can be used to mark the first user as admin). # Those claims are merged with the `EXTRA_CLAIMS`. FIRST_USER_CLAIMS='{}' +# If this is not empty, calls to `/jwt` without an `Authorization` header will still create a jwt (with `null` in `sub`) +GUEST_CLAIMS="" # The url you can use to reach your kyoo instance. This is used during oidc to redirect users to your instance. PUBLIC_URL=http://localhost:8901 diff --git a/auth/README.md b/auth/README.md index 4ffb3538..12391e4d 100644 --- a/auth/README.md +++ b/auth/README.md @@ -10,6 +10,7 @@ - Username/password login - OIDC (login via Google, Discord, Authentik, whatever) - Custom jwt claims (for your role/permissions handling or something else) +- Guest handling (only if using `GUEST_CLAIMS`) - Api keys support - Optionally [Federated](#federated) diff --git a/auth/config.go b/auth/config.go index 7c1d32b7..ed3cc061 100644 --- a/auth/config.go +++ b/auth/config.go @@ -21,6 +21,7 @@ type Configuration struct { PublicUrl string DefaultClaims jwt.MapClaims FirstUserClaims jwt.MapClaims + GuestClaims jwt.MapClaims ExpirationDelay time.Duration } @@ -55,6 +56,14 @@ func LoadConfiguration(db *dbc.Queries) (*Configuration, error) { ret.FirstUserClaims = ret.DefaultClaims } + claims = os.Getenv("GUEST_CLAIMS") + if claims != "" { + err := json.Unmarshal([]byte(claims), &ret.GuestClaims) + if err != nil { + return nil, err + } + } + rsa_pk_path := os.Getenv("RSA_PRIVATE_KEY_PATH") if rsa_pk_path != "" { privateKeyData, err := os.ReadFile(rsa_pk_path) diff --git a/auth/jwt.go b/auth/jwt.go index 7afa24a5..41d43d29 100644 --- a/auth/jwt.go +++ b/auth/jwt.go @@ -29,22 +29,52 @@ type Jwt struct { // @Router /jwt [get] func (h *Handler) CreateJwt(c echo.Context) error { auth := c.Request().Header.Get("Authorization") + var jwt *string + if !strings.HasPrefix(auth, "Bearer ") { - return c.JSON(http.StatusOK, Jwt{Token: nil}) - } - token := auth[len("Bearer "):] + jwt = h.createGuestJwt() + } else { + token := auth[len("Bearer "):] - jwt, err := h.createJwt(token) - if err != nil { - return err + tkn, err := h.createJwt(token) + if err != nil { + return err + } + jwt = &tkn } - c.Response().Header().Add("Authorization", fmt.Sprintf("Bearer %s", jwt)) + if jwt != nil { + c.Response().Header().Add("Authorization", fmt.Sprintf("Bearer %s", *jwt)) + } return c.JSON(http.StatusOK, Jwt{ - Token: &jwt, + Token: jwt, }) } +func (h *Handler) createGuestJwt() *string { + if h.config.GuestClaims == nil { + return nil + } + + claims := maps.Clone(h.config.GuestClaims) + claims["username"] = "guest" + claims["sub"] = "guest" + claims["sid"] = "guest" + claims["iss"] = h.config.PublicUrl + claims["iat"] = &jwt.NumericDate{ + Time: time.Now().UTC(), + } + claims["exp"] = &jwt.NumericDate{ + Time: time.Now().UTC().Add(time.Hour), + } + jwt := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + t, err := jwt.SignedString(h.config.JwtPrivateKey) + if err != nil { + return nil + } + return &t +} + func (h *Handler) createJwt(token string) (string, error) { session, err := h.db.GetUserFromToken(context.Background(), token) if err != nil { diff --git a/auth/main.go b/auth/main.go index dc4eca62..6c986b46 100644 --- a/auth/main.go +++ b/auth/main.go @@ -128,23 +128,28 @@ type Handler struct { func (h *Handler) TokenToJwt(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { auth := c.Request().Header.Get("Authorization") + var jwt *string + if auth == "" || !strings.HasPrefix(auth, "Bearer ") { - return next(c) - } - token := auth[len("Bearer "):] + jwt = h.createGuestJwt() + } else { + token := auth[len("Bearer "):] + // this is only used to check if it is a session token or a jwt + _, err := base64.RawURLEncoding.DecodeString(token) + if err != nil { + return next(c) + } - // this is only used to check if it is a session token or a jwt - _, err := base64.RawURLEncoding.DecodeString(token) - if err != nil { - return next(c) + tkn, err := h.createJwt(token) + if err != nil { + return err + } + jwt = &tkn } - jwt, err := h.createJwt(token) - if err != nil { - return err + if jwt != nil { + c.Request().Header.Set("Authorization", *jwt) } - c.Request().Header.Set("Authorization", jwt) - return next(c) } }