Skip to content

Commit 97e90ea

Browse files
committed
feat: implement basic oidc functionality
1 parent 6ae7c1c commit 97e90ea

17 files changed

Lines changed: 916 additions & 18 deletions

frontend/src/pages/authorize-page.tsx

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useUserContext } from "@/context/user-context";
2-
import { useQuery } from "@tanstack/react-query";
3-
import { Navigate } from "react-router";
2+
import { useMutation, useQuery } from "@tanstack/react-query";
3+
import { Navigate, useNavigate } from "react-router";
44
import { useLocation } from "react-router";
55
import {
66
Card,
@@ -11,6 +11,8 @@ import {
1111
} from "@/components/ui/card";
1212
import { getOidcClientInfoScehma } from "@/schemas/oidc-schemas";
1313
import { Button } from "@/components/ui/button";
14+
import axios from "axios";
15+
import { toast } from "sonner";
1416

1517
type AuthorizePageProps = {
1618
scope: string;
@@ -25,6 +27,7 @@ const optionalAuthorizeProps = ["state"];
2527
export const AuthorizePage = () => {
2628
const { isLoggedIn } = useUserContext();
2729
const { search } = useLocation();
30+
const navigate = useNavigate();
2831

2932
const searchParams = new URLSearchParams(search);
3033

@@ -46,20 +49,46 @@ export const AuthorizePage = () => {
4649
},
4750
});
4851

52+
const authorizeMutation = useMutation({
53+
mutationFn: () => {
54+
return axios.post("/api/oidc/authorize", {
55+
scope: props.scope,
56+
response_type: props.responseType,
57+
client_id: props.clientId,
58+
redirect_uri: props.redirectUri,
59+
state: props.state,
60+
});
61+
},
62+
mutationKey: ["authorize", props.clientId],
63+
onSuccess: (data) => {
64+
toast.info("Authorized", {
65+
description: "You will be soon redirected to your application",
66+
});
67+
window.location.replace(
68+
`${data.data.redirect_uri}?code=${encodeURIComponent(data.data.code)}&state=${encodeURIComponent(data.data.state)}`,
69+
);
70+
},
71+
onError: (error) => {
72+
window.location.replace(
73+
`/error?error=${encodeURIComponent(error.message)}`,
74+
);
75+
},
76+
});
77+
4978
if (!isLoggedIn) {
5079
// TODO: Pass the params to the login page, so user can login -> authorize
5180
return <Navigate to="/login" replace />;
5281
}
5382

54-
for (const key in Object.keys(props)) {
83+
Object.keys(props).forEach((key) => {
5584
if (
5685
!props[key as keyof AuthorizePageProps] &&
5786
!optionalAuthorizeProps.includes(key)
5887
) {
5988
// TODO: Add reason for error
6089
return <Navigate to="/error" replace />;
6190
}
62-
}
91+
});
6392

6493
if (getClientInfo.isLoading) {
6594
return (
@@ -91,8 +120,19 @@ export const AuthorizePage = () => {
91120
</CardDescription>
92121
</CardHeader>
93122
<CardFooter className="flex flex-col items-stretch gap-2">
94-
<Button>Authorize</Button>
95-
<Button variant="outline">Cancel</Button>
123+
<Button
124+
onClick={() => authorizeMutation.mutate()}
125+
loading={authorizeMutation.isPending}
126+
>
127+
Authorize
128+
</Button>
129+
<Button
130+
onClick={() => navigate("/")}
131+
disabled={authorizeMutation.isPending}
132+
variant="outline"
133+
>
134+
Cancel
135+
</Button>
96136
</CardFooter>
97137
</Card>
98138
);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
DROP TABLE IF EXISTS "oidc_tokens";
2+
DROP TABLE IF EXISTS "oidc_userinfo";
3+
DROP TABLE IF EXISTS "oidc_codes";
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
CREATE TABLE IF NOT EXISTS "oidc_codes" (
2+
"sub" TEXT NOT NULL UNIQUE,
3+
"code" TEXT NOT NULL PRIMARY KEY UNIQUE,
4+
"scope" TEXT NOT NULL,
5+
"redirect_uri" TEXT NOT NULL,
6+
"client_id" TEXT NOT NULL,
7+
"expires_at" INTEGER NOT NULL
8+
);
9+
10+
CREATE TABLE IF NOT EXISTS "oidc_tokens" (
11+
"sub" TEXT NOT NULL UNIQUE,
12+
"access_token" TEXT NOT NULL PRIMARY KEY UNIQUE,
13+
"scope" TEXT NOT NULL,
14+
"client_id" TEXT NOT NULL,
15+
"expires_at" INTEGER NOT NULL
16+
);
17+
18+
CREATE TABLE IF NOT EXISTS "oidc_userinfo" (
19+
"sub" TEXT NOT NULL UNIQUE PRIMARY KEY,
20+
"name" TEXT NOT NULL,
21+
"preferred_username" TEXT NOT NULL,
22+
"email" TEXT NOT NULL,
23+
"groups" TEXT NOT NULL,
24+
"updated_at" INTEGER NOT NULL
25+
);

internal/bootstrap/app_bootstrap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ func (app *BootstrapApp) Setup() error {
176176
app.context.configuredProviders = configuredProviders
177177

178178
// Setup router
179-
router, err := app.setupRouter()
179+
router, err := app.setupRouter(queries)
180180

181181
if err != nil {
182182
return fmt.Errorf("failed to setup routes: %w", err)

internal/bootstrap/router_bootstrap.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import (
77
"github.com/steveiliop56/tinyauth/internal/config"
88
"github.com/steveiliop56/tinyauth/internal/controller"
99
"github.com/steveiliop56/tinyauth/internal/middleware"
10+
"github.com/steveiliop56/tinyauth/internal/repository"
1011

1112
"github.com/gin-gonic/gin"
1213
)
1314

1415
var DEV_MODES = []string{"main", "test", "development"}
1516

16-
func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
17+
func (app *BootstrapApp) setupRouter(queries *repository.Queries) (*gin.Engine, error) {
1718
if !slices.Contains(DEV_MODES, config.Version) {
1819
gin.SetMode(gin.ReleaseMode)
1920
}
@@ -88,7 +89,8 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
8889

8990
oidcController := controller.NewOIDCController(controller.OIDCControllerConfig{
9091
Clients: app.context.oidcClients,
91-
}, apiRouter)
92+
AppURL: app.config.AppURL,
93+
}, apiRouter, queries)
9294

9395
oidcController.SetupRoutes()
9496

0 commit comments

Comments
 (0)