Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refacto: use hono fire in the oven #5

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
CALLBACK_URL: /login-callback
HOST: http://localhost:3000
LOGIN_HINT: ""
MCP_CLIENT_ID: client_id
MCP_CLIENT_SECRET: client_secret
MCP_ID_TOKEN_SIGNED_RESPONSE_ALG: RS256
MCP_PROVIDER: https://app-test.moncomptepro.beta.gouv.fr/
MCP_SCOPES: "openid email profile organization"
MCP_USERINFO_SIGNED_RESPONSE_ALG: ""
PORT: 3000
SITE_TITLE: "Bonjour monde !"
STYLESHEET_URL: https://unpkg.com/bamboo.css
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- run: bun install
- run: bun x tsc --noEmit

e2e:
runs-on: ubuntu-latest
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This tool is full configured using environment variables.
Pull the image:

```
docker pull rdubigny/moncomptepro-test-client
docker pull ghcr.io/betagouv/moncomptepro-test-client
```

Run the container:
Expand All @@ -24,7 +24,7 @@ Run the container:
docker run -d --rm \
-p 3000:3000 \
-e PORT=3000 \
rdubigny/moncomptepro-test-client
ghcr.io/betagouv/moncomptepro-test-client
```

## Run it with Docker Compose
Expand All @@ -36,7 +36,7 @@ version: "3.5"

services:
oidc-test-client:
image: rdubigny/moncomptepro-test-client
image: ghcr.io/betagouv/moncomptepro-test-client
ports:
- 3000:3000
environment:
Expand Down
Binary file modified bun.lockb
Binary file not shown.
151 changes: 84 additions & 67 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
//

import fastify_cookie, { type FastifyCookieOptions } from "@fastify/cookie";
import fastify_formbody from "@fastify/formbody";
import fastify_session, { type FastifySessionOptions } from "@fastify/session";
import pointOfView from "@fastify/view";
import ejs from "ejs";
import Fastify from "fastify";
import { Hono, type Env } from "hono";
import { CookieStore, Session, sessionMiddleware } from "hono-sessions";
import { logger } from "hono/logger";
import { ok } from "node:assert";
import { env as process_env } from "node:process";
import {
Expand All @@ -16,6 +13,7 @@ import {
} from "openid-client";
import Youch from "youch";
import { z } from "zod";
import { Index } from "./views";

//

Expand All @@ -31,7 +29,7 @@ const env = z
MCP_SCOPES: z.string().default("openid email profile organization"),
MCP_USERINFO_SIGNED_RESPONSE_ALG: z.string().optional(),
PORT: z.coerce.number().default(3000),
SITE_TITLE: z.string().default("Bonjour monde !"),
SITE_TITLE: z.string().default("Bonjour monde !"),
STYLESHEET_URL: z.string().default("https://unpkg.com/bamboo.css"),
})
.parse(process_env);
Expand All @@ -40,40 +38,61 @@ const redirectUri = `${env.HOST}${env.CALLBACK_URL}`;

//

const fastify = Fastify({ logger: true });
fastify.register(pointOfView, { engine: { ejs } });
fastify.register(fastify_formbody);
fastify.register(fastify_cookie, {} as FastifyCookieOptions);

declare module "fastify" {
interface Session {
verifier: string;
userinfo: string;
idtoken: IdTokenClaims;
oauth2token: TokenSet;
}
interface Session_Context extends Env {
Variables: {
session: Session & {
get(key: "verifier"): string;
set(key: "verifier", value: string): void;
} & {
get(key: "userinfo"): string;
set(key: "userinfo", value: string): void;
} & {
get(key: "idtoken"): IdTokenClaims;
set(key: "idtoken", value: IdTokenClaims): void;
} & {
get(key: "oauth2token"): TokenSet;
set(key: "oauth2token", value: TokenSet): void;
};
};
}

fastify.register(fastify_session, {
cookieName: "mcp_session",
secret: ["key1", "key2"],
cookie: { secure: "auto" },
} as FastifySessionOptions);

fastify.get("/", function (req, reply) {
reply.view("/views/index.ejs", {
title: env.SITE_TITLE,
stylesheet_url: env.STYLESHEET_URL,
userinfo: JSON.stringify(req.session.userinfo, null, 2),
idtoken: JSON.stringify(req.session.idtoken, null, 2),
oauth2token: JSON.stringify(req.session.oauth2token, null, 2),
});
const hono = new Hono<Session_Context>();

//

hono.use("*", logger());

hono.use(
"*",
sessionMiddleware({
store: new CookieStore(),
encryptionKey: "a secret with minimum length of 32 characters",
sessionCookieName: "mcp_session",
}),
);

//

hono.get("/", ({ html, get }) => {
const session = get("session") || new Session();

return html(
Index({
title: env.SITE_TITLE,
stylesheet_url: env.STYLESHEET_URL,
userinfo: session.get("userinfo"),
idtoken: session.get("idtoken"),
oauth2token: session.get("oauth2token"),
}),
);
});

fastify.post("/login", async function (req, reply) {
hono.post("/login", async function ({ redirect, get }) {
const session = get("session");

const client = await getMcpClient();
const code_verifier = generators.codeVerifier();
req.session.verifier = code_verifier;
session.set("verifier", code_verifier);

const code_challenge = generators.codeChallenge(code_verifier);

Expand All @@ -84,29 +103,31 @@ fastify.post("/login", async function (req, reply) {
login_hint: env.LOGIN_HINT,
});

reply.redirect(redirectUrl);
return redirect(redirectUrl);
});

fastify.get(env.CALLBACK_URL, async function (req, reply) {
hono.get(env.CALLBACK_URL, async function ({ req, redirect, get }) {
const session = get("session");
const client = await getMcpClient();
const params = client.callbackParams(req.raw);
const params = client.callbackParams(req.raw.url);
const tokenSet = await client.callback(redirectUri, params, {
code_verifier: req.session.verifier,
code_verifier: session.get("verifier") as string,
});

ok(tokenSet.access_token, "Missing tokenSet.access_token");

req.session.userinfo = await client.userinfo(tokenSet.access_token);
req.session.idtoken = tokenSet.claims();
req.session.oauth2token = tokenSet;
session.set("userinfo", await client.userinfo(tokenSet.access_token));
session.set("idtoken", tokenSet.claims());
session.set("oauth2token", tokenSet);

reply.redirect("/");
return redirect("/");
});

fastify.post("/select-organization", async function (req, reply) {
hono.post("/select-organization", async function ({ req, redirect, get }) {
const session = get("session");
const client = await getMcpClient();
const code_verifier = generators.codeVerifier();
req.session.verifier = code_verifier;
session.set("verifier", code_verifier);
const code_challenge = generators.codeChallenge(code_verifier);

const redirectUrl = client.authorizationUrl({
Expand All @@ -116,13 +137,14 @@ fastify.post("/select-organization", async function (req, reply) {
prompt: "select_organization",
});

reply.redirect(redirectUrl);
return redirect(redirectUrl);
});

fastify.post("/update-userinfo", async (req, reply) => {
hono.post("/update-userinfo", async ({ get, redirect }) => {
const session = get("session");
const client = await getMcpClient();
const code_verifier = generators.codeVerifier();
req.session.verifier = code_verifier;
session.set("verifier", code_verifier);
const code_challenge = generators.codeChallenge(code_verifier);

const redirectUrl = client.authorizationUrl({
Expand All @@ -132,23 +154,26 @@ fastify.post("/update-userinfo", async (req, reply) => {
prompt: "update_userinfo",
});

reply.redirect(redirectUrl);
return redirect(redirectUrl);
});

fastify.post("/logout", async (req, reply) => {
await req.session.destroy();
hono.post("/logout", async ({ get, redirect }) => {
const session = get("session");
session.deleteSession();

const client = await getMcpClient();
const redirectUrl = client.endSessionUrl({
post_logout_redirect_uri: `${env.HOST}/`,
});

reply.redirect(redirectUrl);
return redirect(redirectUrl);
});

fastify.post("/force-login", async (req, reply) => {
hono.post("/force-login", async ({ get, redirect }) => {
const session = get("session");
const client = await getMcpClient();
const code_verifier = generators.codeVerifier();
req.session.verifier = code_verifier;
session.set("verifier", code_verifier);
const code_challenge = generators.codeChallenge(code_verifier);

const redirectUrl = client.authorizationUrl({
Expand All @@ -161,23 +186,15 @@ fastify.post("/force-login", async (req, reply) => {
// if so, claims parameter is not necessary as auth_time will be returned
});

reply.redirect(redirectUrl);
return redirect(redirectUrl);
});

fastify.setErrorHandler(async function (error, request, reply) {
try {
const youch = new Youch(error, request.raw);
const html = await youch.toHTML();
reply.type("text/html").send(html);
} catch (error) {
reply.send(error);
}
hono.onError(async (error, { html, req }) => {
const youch = new Youch(error, req.raw);
return html(await youch.toHTML());
});

fastify.listen({ port: env.PORT }, () => {
console.log(`App listening on port ${env.PORT}`);
console.log(env);
});
export default hono;

//

Expand Down
15 changes: 6 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,20 @@
},
"homepage": "https://github.com/betagouv/moncomptepro-test-client#readme",
"dependencies": {
"@fastify/cookie": "^9.2.0",
"@fastify/formbody": "^7.4.0",
"@fastify/session": "^10.7.0",
"@fastify/view": "^8.2.0",
"ejs": "^3.1.9",
"fastify": "^4.25.0",
"fastify-error-page": "^4.0.0",
"hono": "^3.11.7",
"hono-sessions": "^0.3.3",
"openid-client": "^5.6.1",
"youch": "^3.3.3",
"zod": "^3.22.4"
},
"devDependencies": {
"@tsconfig/bun": "^1.0.1",
"@types/ejs": "^3.1.5",
"bun-types": "^1.0.17",
"bun-types": "^1.0.18",
"prettier": "^3.1.1"
},
"overrides": {
"hono": "3.11.7"
},
"engines": {
"node": "20",
"npm": ">=9"
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"verbatimModuleSyntax": true
}
},
"files": ["./index.ts"]
}
13 changes: 13 additions & 0 deletions views/critical.css

Large diffs are not rendered by default.

71 changes: 0 additions & 71 deletions views/index.ejs

This file was deleted.

Loading