mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-01-25 16:52:29 +00:00
wip: authorize page
This commit is contained in:
@@ -17,6 +17,7 @@ import { AppContextProvider } from "./context/app-context.tsx";
|
|||||||
import { UserContextProvider } from "./context/user-context.tsx";
|
import { UserContextProvider } from "./context/user-context.tsx";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { ThemeProvider } from "./components/providers/theme-provider.tsx";
|
import { ThemeProvider } from "./components/providers/theme-provider.tsx";
|
||||||
|
import { AuthorizePage } from "./pages/authorize-page.tsx";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ createRoot(document.getElementById("root")!).render(
|
|||||||
<Route element={<Layout />} errorElement={<ErrorPage />}>
|
<Route element={<Layout />} errorElement={<ErrorPage />}>
|
||||||
<Route path="/" element={<App />} />
|
<Route path="/" element={<App />} />
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
<Route path="/authorize" element={<AuthorizePage />} />
|
||||||
<Route path="/logout" element={<LogoutPage />} />
|
<Route path="/logout" element={<LogoutPage />} />
|
||||||
<Route path="/continue" element={<ContinuePage />} />
|
<Route path="/continue" element={<ContinuePage />} />
|
||||||
<Route path="/totp" element={<TotpPage />} />
|
<Route path="/totp" element={<TotpPage />} />
|
||||||
|
|||||||
99
frontend/src/pages/authorize-page.tsx
Normal file
99
frontend/src/pages/authorize-page.tsx
Normal file
@@ -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 <Navigate to="/login" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in Object.keys(props)) {
|
||||||
|
if (
|
||||||
|
!props[key as keyof AuthorizePageProps] &&
|
||||||
|
!optionalAuthorizeProps.includes(key)
|
||||||
|
) {
|
||||||
|
// TODO: Add reason for error
|
||||||
|
return <Navigate to="/error" replace />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getClientInfo.isLoading) {
|
||||||
|
return (
|
||||||
|
<Card className="min-w-xs sm:min-w-sm">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-3xl">Loading...</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Please wait while we load the client information.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getClientInfo.isError) {
|
||||||
|
// TODO: Add reason for error
|
||||||
|
return <Navigate to="/error" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="min-w-xs sm:min-w-sm">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-3xl">
|
||||||
|
Continue to {getClientInfo.data?.name || "Unknown"}?
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Would you like to continue to this app? Please keep in mind that this
|
||||||
|
app will have access to your email and other information.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter className="flex flex-col items-stretch gap-2">
|
||||||
|
<Button>Authorize</Button>
|
||||||
|
<Button variant="outline">Cancel</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
5
frontend/src/schemas/oidc-schemas.ts
Normal file
5
frontend/src/schemas/oidc-schemas.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const getOidcClientInfoScehma = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
});
|
||||||
@@ -30,6 +30,7 @@ type BootstrapApp struct {
|
|||||||
users []config.User
|
users []config.User
|
||||||
oauthProviders map[string]config.OAuthServiceConfig
|
oauthProviders map[string]config.OAuthServiceConfig
|
||||||
configuredProviders []controller.Provider
|
configuredProviders []controller.Provider
|
||||||
|
oidcClients []config.OIDCClientConfig
|
||||||
}
|
}
|
||||||
services Services
|
services Services
|
||||||
}
|
}
|
||||||
@@ -84,6 +85,12 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
app.context.oauthProviders[id] = provider
|
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
|
// Get cookie domain
|
||||||
cookieDomain, err := utils.GetCookieDomain(app.config.AppURL)
|
cookieDomain, err := utils.GetCookieDomain(app.config.AppURL)
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
|
|||||||
|
|
||||||
oauthController.SetupRoutes()
|
oauthController.SetupRoutes()
|
||||||
|
|
||||||
|
oidcController := controller.NewOIDCController(controller.OIDCControllerConfig{
|
||||||
|
Clients: app.context.oidcClients,
|
||||||
|
}, apiRouter)
|
||||||
|
|
||||||
|
oidcController.SetupRoutes()
|
||||||
|
|
||||||
proxyController := controller.NewProxyController(controller.ProxyControllerConfig{
|
proxyController := controller.NewProxyController(controller.ProxyControllerConfig{
|
||||||
AppURL: app.config.AppURL,
|
AppURL: app.config.AppURL,
|
||||||
}, apiRouter, app.services.accessControlService, app.services.authService)
|
}, apiRouter, app.services.accessControlService, app.services.authService)
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ type OAuthServiceConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OIDCClientConfig struct {
|
type OIDCClientConfig struct {
|
||||||
|
ID string `description:"OIDC client ID." yaml:"-"`
|
||||||
ClientID string `description:"OIDC client ID." yaml:"clientId"`
|
ClientID string `description:"OIDC client ID." yaml:"clientId"`
|
||||||
ClientSecret string `description:"OIDC client secret." yaml:"clientSecret"`
|
ClientSecret string `description:"OIDC client secret." yaml:"clientSecret"`
|
||||||
ClientSecretFile string `description:"Path to the file containing the OIDC client secret." yaml:"clientSecretFile"`
|
ClientSecretFile string `description:"Path to the file containing the OIDC client secret." yaml:"clientSecretFile"`
|
||||||
|
|||||||
71
internal/controller/oidc_controller.go
Normal file
71
internal/controller/oidc_controller.go
Normal file
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user