diff --git a/cmd/root.go b/cmd/root.go index 4be9b1b..4c612be 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -78,8 +78,15 @@ var rootCmd = &cobra.Command{ log.Debug().Msg("Parsed OAuth config") + // Create docker service + docker := docker.NewDocker() + + // Initialize docker + dockerErr := docker.Init() + HandleError(dockerErr, "Failed to initialize docker") + // Create auth service - auth := auth.NewAuth(users, oauthWhitelist) + auth := auth.NewAuth(docker, users, oauthWhitelist) // Create OAuth providers service providers := providers.NewProviders(oauthConfig) @@ -90,13 +97,6 @@ var rootCmd = &cobra.Command{ // Create hooks service hooks := hooks.NewHooks(auth, providers) - // Create docker service - docker := docker.NewDocker() - - // Initialize docker - dockerErr := docker.Init() - HandleError(dockerErr, "Failed to initialize docker") - // Create API api := api.NewAPI(types.APIConfig{ Port: config.Port, @@ -113,18 +113,7 @@ var rootCmd = &cobra.Command{ api.SetupRoutes() // Start - // api.Run() - containers, err := docker.GetContainers() - HandleError(err, "Failed to get containers") - - for _, container := range containers { - log.Debug().Str("container", container.ID).Msg("Found container") - inspect, err := docker.InspectContainer(container.ID) - HandleError(err, "Failed to inspect container") - log.Debug().Str("container", container.ID).Str("name", inspect.Name).Interface("labels", container.Labels).Msg("Inspected container") - labels := utils.GetTinyauthLabels(inspect.Config.Labels) - log.Debug().Interface("labels", labels).Msg("Parsed labels") - } + api.Run() }, } diff --git a/internal/api/api.go b/internal/api/api.go index 6088bc9..8839ea4 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -98,8 +98,30 @@ func (api *API) SetupRoutes() { log.Debug().Msg("Checking auth") userContext := api.Hooks.UseUserContext(c) + uri := c.Request.Header.Get("X-Forwarded-Uri") + proto := c.Request.Header.Get("X-Forwarded-Proto") + host := c.Request.Header.Get("X-Forwarded-Host") + if userContext.IsLoggedIn { log.Debug().Msg("Authenticated") + + appAllowed, appAllowedErr := api.Auth.ResourceAllowed(userContext, host) + if handleApiError(c, "Failed to check if resource is allowed", appAllowedErr) { + return + } + + if !appAllowed { + log.Warn().Str("username", userContext.Username).Str("host", host).Msg("User not allowed") + queries, queryErr := query.Values(types.UnauthorizedQuery{ + Username: userContext.Username, + Resource: strings.Split(host, ".")[0], + }) + if handleApiError(c, "Failed to build query", queryErr) { + return + } + c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", api.Config.AppURL, queries.Encode())) + } + c.JSON(200, gin.H{ "status": 200, "message": "Authenticated", @@ -107,9 +129,6 @@ func (api *API) SetupRoutes() { return } - uri := c.Request.Header.Get("X-Forwarded-Uri") - proto := c.Request.Header.Get("X-Forwarded-Proto") - host := c.Request.Header.Get("X-Forwarded-Host") queries, queryErr := query.Values(types.LoginQuery{ RedirectURI: fmt.Sprintf("%s://%s%s", proto, host, uri), }) diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 6ed71a5..554cafa 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -1,7 +1,11 @@ package auth import ( + "slices" + "strings" + "tinyauth/internal/docker" "tinyauth/internal/types" + "tinyauth/internal/utils" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" @@ -9,8 +13,9 @@ import ( "golang.org/x/crypto/bcrypt" ) -func NewAuth(userList types.Users, oauthWhitelist []string) *Auth { +func NewAuth(docker *docker.Docker, userList types.Users, oauthWhitelist []string) *Auth { return &Auth{ + Docker: docker, Users: userList, OAuthWhitelist: oauthWhitelist, } @@ -18,6 +23,7 @@ func NewAuth(userList types.Users, oauthWhitelist []string) *Auth { type Auth struct { Users types.Users + Docker *docker.Docker OAuthWhitelist []string } @@ -89,3 +95,53 @@ func (auth *Auth) GetSessionCookie(c *gin.Context) (types.SessionCookie, error) func (auth *Auth) UserAuthConfigured() bool { return len(auth.Users) > 0 } + +func (auth *Auth) ResourceAllowed(context types.UserContext, host string) (bool, error) { + appId := strings.Split(host, ".")[0] + containers, containersErr := auth.Docker.GetContainers() + + if containersErr != nil { + return false, containersErr + } + + log.Debug().Msg("Got containers") + + for _, container := range containers { + inspect, inspectErr := auth.Docker.InspectContainer(container.ID) + + if inspectErr != nil { + return false, inspectErr + } + + containerName := strings.Split(inspect.Name, "/")[1] + + if containerName == appId { + log.Debug().Str("container", containerName).Msg("Found container") + + labels := utils.GetTinyauthLabels(inspect.Config.Labels) + + log.Debug().Msg("Got labels") + + if context.OAuth && len(labels.OAuthWhitelist) != 0 { + log.Debug().Msg("Checking OAuth whitelist") + if slices.Contains(labels.OAuthWhitelist, context.Username) { + return true, nil + } + return false, nil + } + + if len(labels.Users) != 0 { + log.Debug().Msg("Checking users") + if slices.Contains(labels.Users, context.Username) { + return true, nil + } + return false, nil + } + } + + } + + log.Debug().Msg("No matching container found, allowing access") + + return true, nil +} diff --git a/internal/types/types.go b/internal/types/types.go index 951d549..bbc0542 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -89,6 +89,7 @@ type OAuthProviders struct { type UnauthorizedQuery struct { Username string `url:"username"` + Resource string `url:"resource"` } type SessionCookie struct { diff --git a/site/src/pages/unauthorized-page.tsx b/site/src/pages/unauthorized-page.tsx index 2a92f74..7c3b8a8 100644 --- a/site/src/pages/unauthorized-page.tsx +++ b/site/src/pages/unauthorized-page.tsx @@ -1,18 +1,12 @@ import { Button, Code, Paper, Text } from "@mantine/core"; import { Layout } from "../components/layouts/layout"; -import { useUserContext } from "../context/user-context"; import { Navigate } from "react-router"; export const UnauthorizedPage = () => { const queryString = window.location.search; const params = new URLSearchParams(queryString); const username = params.get("username"); - - const { isLoggedIn } = useUserContext(); - - if (isLoggedIn) { - return ; - } + const resource = params.get("resource"); if (username === "null") { return ; @@ -25,8 +19,14 @@ export const UnauthorizedPage = () => { Unauthorized - The user with username {username} is not authorized to - login. + The user with username {username} is not authorized to{" "} + {resource !== "null" ? ( + + access the {resource} resource. + + ) : ( + "login." + )}