Skip to content

Commit

Permalink
refacto: use hono fire in the oven
Browse files Browse the repository at this point in the history
  • Loading branch information
douglasduteil committed Dec 14, 2023
1 parent 7890ff6 commit 2a169ca
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 146 deletions.
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
Binary file modified bun.lockb
Binary file not shown.
148 changes: 82 additions & 66 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 @@ -40,40 +38,60 @@ 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;
};
};
}
const hono = new Hono<Session_Context>();

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),
});
//

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 +102,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 +136,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 +153,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 +185,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
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.

90 changes: 90 additions & 0 deletions views/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//

import { html } from "hono/html";
import { readFile } from "node:fs/promises";
import type { IdTokenClaims, TokenSet } from "openid-client";

//

const critical_css = await readFile(import.meta.resolveSync("./critical.css"));

//

export function Index(locals: {
title: string;
stylesheet_url: string;
userinfo: string;
idtoken: IdTokenClaims;
oauth2token: TokenSet;
}) {
const user_info = locals.userinfo
? html`<h2>Information utilisateur</h2>
<pre><code>${JSON.stringify(locals.userinfo, null, 2)}</code></pre>`
: "";
const idtoken = locals.idtoken
? html`<h2>ID Token</h2>
<pre><code>${JSON.stringify(locals.idtoken, null, 2)}</code></pre>`
: "";
const oauth2token = locals.oauth2token
? html`<h2>OAuth2 Token</h2>
<pre><code>${JSON.stringify(locals.oauth2token, null, 2)}</code></pre>`
: "";

return html`<!doctype html>
<html lang="fr-FR">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>${locals.title}</title>
<style>
${critical_css}
</style>
<link rel="stylesheet" href="${locals.stylesheet_url}" />
</head>
<body>
<h1>${locals.title}</h1>
<h2>Se connecter</h2>
<div>
<form action="/login" method="post">
<button id="login" class="moncomptepro-button"></button>
</form>
<p>
<a
href="https://moncomptepro.beta.gouv.fr/"
target="_blank"
rel="noopener noreferrer"
title="Qu’est-ce que MonComptePro ? - nouvelle fenêtre"
>
Qu’est-ce que MonComptePro ?
</a>
</p>
</div>
${user_info} ${idtoken} ${oauth2token}
<h2>Interactions</h2>
<form action="/logout" method="post">
<button id="logout">Se déconnecter</button>
</form>
<br />
<form action="/select-organization" method="post">
<button id="select-organization">Changer d’organisation</button>
</form>
<br />
<form action="/update-userinfo" method="post">
<button id="update-userinfo">Mettre à jour mes informations</button>
</form>
<br />
<form action="/force-login" method="post">
<button id="force-login">Forcer une reconnexion</button>
</form>
<br />
<footer>
<p>
Source:
<a href="https://github.com/betagouv/moncomptepro-test-client"
>github.com/betagouv/moncomptepro-test-client</a
>
</p>
</footer>
</body>
</html>`;
}

0 comments on commit 2a169ca

Please sign in to comment.