diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 0d20de8..cd89829 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -17,6 +17,7 @@ import { AppContextProvider } from "./context/app-context.tsx";
import { UserContextProvider } from "./context/user-context.tsx";
import { Toaster } from "@/components/ui/sonner";
import { ThemeProvider } from "./components/providers/theme-provider.tsx";
+import { AuthorizePage } from "./pages/authorize-page.tsx";
const queryClient = new QueryClient();
@@ -31,6 +32,7 @@ createRoot(document.getElementById("root")!).render(
} errorElement={}>
} />
} />
+ } />
} />
} />
} />
diff --git a/frontend/src/pages/authorize-page.tsx b/frontend/src/pages/authorize-page.tsx
new file mode 100644
index 0000000..6befa96
--- /dev/null
+++ b/frontend/src/pages/authorize-page.tsx
@@ -0,0 +1,99 @@
+import { useUserContext } from "@/context/user-context";
+import { useQuery } from "@tanstack/react-query";
+import { Navigate } from "react-router";
+import { useLocation } from "react-router";
+import {
+ Card,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+ CardFooter,
+} from "@/components/ui/card";
+import { getOidcClientInfoScehma } from "@/schemas/oidc-schemas";
+import { Button } from "@/components/ui/button";
+
+type AuthorizePageProps = {
+ scope: string;
+ responseType: string;
+ clientId: string;
+ redirectUri: string;
+ state: string;
+};
+
+const optionalAuthorizeProps = ["state"];
+
+export const AuthorizePage = () => {
+ const { isLoggedIn } = useUserContext();
+ const { search } = useLocation();
+
+ const searchParams = new URLSearchParams(search);
+
+ // If there is a better way to do this, please do let me know
+ const props: AuthorizePageProps = {
+ scope: searchParams.get("scope") || "",
+ responseType: searchParams.get("response_type") || "",
+ clientId: searchParams.get("client_id") || "",
+ redirectUri: searchParams.get("redirect_uri") || "",
+ state: searchParams.get("state") || "",
+ };
+
+ const getClientInfo = useQuery({
+ queryKey: ["client", props.clientId],
+ queryFn: async () => {
+ const res = await fetch(`/api/oidc/clients/${props.clientId}`);
+ const data = await getOidcClientInfoScehma.parseAsync(await res.json());
+ return data;
+ },
+ });
+
+ if (!isLoggedIn) {
+ // TODO: Pass the params to the login page, so user can login -> authorize
+ return ;
+ }
+
+ for (const key in Object.keys(props)) {
+ if (
+ !props[key as keyof AuthorizePageProps] &&
+ !optionalAuthorizeProps.includes(key)
+ ) {
+ // TODO: Add reason for error
+ return ;
+ }
+ }
+
+ if (getClientInfo.isLoading) {
+ return (
+
+
+ Loading...
+
+ Please wait while we load the client information.
+
+
+
+ );
+ }
+
+ if (getClientInfo.isError) {
+ // TODO: Add reason for error
+ return ;
+ }
+
+ return (
+
+
+
+ Continue to {getClientInfo.data?.name || "Unknown"}?
+
+
+ Would you like to continue to this app? Please keep in mind that this
+ app will have access to your email and other information.
+
+
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/schemas/oidc-schemas.ts b/frontend/src/schemas/oidc-schemas.ts
new file mode 100644
index 0000000..853745c
--- /dev/null
+++ b/frontend/src/schemas/oidc-schemas.ts
@@ -0,0 +1,5 @@
+import { z } from "zod";
+
+export const getOidcClientInfoScehma = z.object({
+ name: z.string(),
+});
diff --git a/internal/bootstrap/app_bootstrap.go b/internal/bootstrap/app_bootstrap.go
index f1c4b0b..e9cdd5a 100644
--- a/internal/bootstrap/app_bootstrap.go
+++ b/internal/bootstrap/app_bootstrap.go
@@ -30,6 +30,7 @@ type BootstrapApp struct {
users []config.User
oauthProviders map[string]config.OAuthServiceConfig
configuredProviders []controller.Provider
+ oidcClients []config.OIDCClientConfig
}
services Services
}
@@ -84,6 +85,12 @@ func (app *BootstrapApp) Setup() error {
app.context.oauthProviders[id] = provider
}
+ // Setup OIDC clients
+ for id, client := range app.config.OIDC.Clients {
+ client.ID = id
+ app.context.oidcClients = append(app.context.oidcClients, client)
+ }
+
// Get cookie domain
cookieDomain, err := utils.GetCookieDomain(app.config.AppURL)
diff --git a/internal/bootstrap/router_bootstrap.go b/internal/bootstrap/router_bootstrap.go
index f96670e..c854c45 100644
--- a/internal/bootstrap/router_bootstrap.go
+++ b/internal/bootstrap/router_bootstrap.go
@@ -86,6 +86,12 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
oauthController.SetupRoutes()
+ oidcController := controller.NewOIDCController(controller.OIDCControllerConfig{
+ Clients: app.context.oidcClients,
+ }, apiRouter)
+
+ oidcController.SetupRoutes()
+
proxyController := controller.NewProxyController(controller.ProxyControllerConfig{
AppURL: app.config.AppURL,
}, apiRouter, app.services.accessControlService, app.services.authService)
diff --git a/internal/config/config.go b/internal/config/config.go
index 16ad292..de87390 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -132,6 +132,7 @@ type OAuthServiceConfig struct {
}
type OIDCClientConfig struct {
+ ID string `description:"OIDC client ID." yaml:"-"`
ClientID string `description:"OIDC client ID." yaml:"clientId"`
ClientSecret string `description:"OIDC client secret." yaml:"clientSecret"`
ClientSecretFile string `description:"Path to the file containing the OIDC client secret." yaml:"clientSecretFile"`
diff --git a/internal/controller/oidc_controller.go b/internal/controller/oidc_controller.go
new file mode 100644
index 0000000..8fbf2ce
--- /dev/null
+++ b/internal/controller/oidc_controller.go
@@ -0,0 +1,71 @@
+package controller
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/steveiliop56/tinyauth/internal/config"
+ "github.com/steveiliop56/tinyauth/internal/utils/tlog"
+)
+
+type OIDCControllerConfig struct {
+ Clients []config.OIDCClientConfig
+}
+
+type OIDCController struct {
+ clients []config.OIDCClientConfig
+ router *gin.RouterGroup
+}
+
+func NewOIDCController(config OIDCControllerConfig, router *gin.RouterGroup) *OIDCController {
+ return &OIDCController{
+ clients: config.Clients,
+ router: router,
+ }
+}
+
+func (controller *OIDCController) SetupRoutes() {
+ oidcGroup := controller.router.Group("/oidc")
+ oidcGroup.GET("/clients/:id", controller.GetClientInfo)
+}
+
+type ClientRequest struct {
+ ClientID string `uri:"id" binding:"required"`
+}
+
+func (controller *OIDCController) GetClientInfo(c *gin.Context) {
+ var req ClientRequest
+
+ err := c.BindUri(&req)
+ if err != nil {
+ tlog.App.Error().Err(err).Msg("Failed to bind URI")
+ c.JSON(400, gin.H{
+ "status": 400,
+ "message": "Bad Request",
+ })
+ return
+ }
+
+ var client *config.OIDCClientConfig
+
+ // Inefficient yeah, but it will be good until we have thousands of clients
+ for _, clientCfg := range controller.clients {
+ if clientCfg.ClientID == req.ClientID {
+ client = &clientCfg
+ break
+ }
+ }
+
+ if client == nil {
+ tlog.App.Warn().Str("client_id", req.ClientID).Msg("Client not found")
+ c.JSON(404, gin.H{
+ "status": 404,
+ "message": "Client not found",
+ })
+ return
+ }
+
+ c.JSON(200, gin.H{
+ "status": 200,
+ "client": &client.ClientID,
+ "name": &client.Name,
+ })
+}