117 lines
3.3 KiB
TypeScript
117 lines
3.3 KiB
TypeScript
import express from "express";
|
|
import * as dotenv from "dotenv";
|
|
import * as oid from "openid-client";
|
|
import cookieParser from "cookie-parser";
|
|
import { uid } from "uid";
|
|
import jwt, { type JwtPayload } from "jsonwebtoken";
|
|
import assert from "assert";
|
|
|
|
dotenv.config({ quiet: true });
|
|
|
|
const AUTH_CLIENT_PORT = Number(process.env.AUTH_CLIENT_PORT) || 3002;
|
|
const CLIENT_URL = String(process.env.CLIENT_URL) || "http://localhost";
|
|
const AUTHENTICATED_URL =
|
|
String(process.env.AUTHENTICATED_URL) || "http://localhost";
|
|
const CLIENT_SECRET = String(process.env.CLIENT_SECRET);
|
|
assert(CLIENT_SECRET.length > 0);
|
|
|
|
const app = express();
|
|
|
|
const database: Record<
|
|
string,
|
|
{ oidConfig: oid.Configuration; pkceCodeVerifier: string, url: string }
|
|
> = {};
|
|
|
|
app.get("/login", async (req, res) => {
|
|
if (typeof req.query.provider != "string") {
|
|
res.status(400);
|
|
res.end();
|
|
return;
|
|
}
|
|
const cbid = uid();
|
|
const redirect_uri = `${CLIENT_URL}/callback/${cbid}`;
|
|
const provider = new URL(req.query.provider);
|
|
const oidConfig = await oid.dynamicClientRegistration(
|
|
provider,
|
|
{
|
|
redirect_uris: [redirect_uri],
|
|
grant_types: ["authorization_code", "refresh_token"],
|
|
response_types: ["code"],
|
|
token_endpoint_auth_method: "client_secret_basic",
|
|
},
|
|
undefined,
|
|
{ execute: [oid.allowInsecureRequests] },
|
|
);
|
|
const pkceCodeVerifier = oid.randomPKCECodeVerifier();
|
|
const code_challenge = await oid.calculatePKCECodeChallenge(pkceCodeVerifier);
|
|
database[cbid] = { oidConfig, pkceCodeVerifier, url: redirect_uri };
|
|
setTimeout(() => {
|
|
delete database[cbid];
|
|
}, 600000 /* 10 minutes */);
|
|
res.redirect(
|
|
oid.buildAuthorizationUrl(oidConfig, {
|
|
redirect_uri,
|
|
scope: "openid",
|
|
code_challenge,
|
|
code_challenge_method: "S256",
|
|
}).href,
|
|
);
|
|
});
|
|
|
|
app.get("/callback/:cbid", async (req, res) => {
|
|
const cbid = req.params.cbid as string;
|
|
if (!(cbid in database)) {
|
|
res.status(400);
|
|
res.end();
|
|
return;
|
|
}
|
|
const { oidConfig, pkceCodeVerifier, url: redirect_uri } = database[cbid]!;
|
|
const query = (typeof req.query == "object") ? req.query as Record<string, string> : {};
|
|
const url = new URL(redirect_uri + `?${new URLSearchParams(query).toString()}`);
|
|
let tokens = await oid
|
|
.authorizationCodeGrant(oidConfig, url, {
|
|
pkceCodeVerifier,
|
|
idTokenExpected: true,
|
|
})
|
|
.catch((e) => {
|
|
console.error(e);
|
|
});
|
|
// res.cookie("auth_tokens", JSON.stringify(tokens));
|
|
const claims = tokens!.claims();
|
|
if (claims) {
|
|
const cookie = jwt.sign({ tokens, claims }, CLIENT_SECRET);
|
|
res.cookie("auth_cookie", cookie);
|
|
}
|
|
res.redirect(AUTHENTICATED_URL);
|
|
});
|
|
|
|
app.use(cookieParser());
|
|
|
|
app.get("/noauth", (req, res) => {
|
|
res.status(200);
|
|
res.end();
|
|
});
|
|
|
|
app.get("/authorize", (req, res) => {
|
|
try {
|
|
const { tokens, claims } = jwt.verify(
|
|
req.cookies.auth_cookie,
|
|
CLIENT_SECRET,
|
|
) as JwtPayload;
|
|
console.log(`Authorized '${claims.sub}' from ${claims.iss}`);
|
|
res.setHeader("AuthSub", claims.sub);
|
|
res.setHeader("AuthIssuer", claims.iss);
|
|
} catch (e) {
|
|
res.status(401);
|
|
res.send("Not Authorized");
|
|
}
|
|
res.end();
|
|
});
|
|
|
|
app.listen(AUTH_CLIENT_PORT, (err) => {
|
|
if (!err) {
|
|
console.log(`Auth client listening on port ${AUTH_CLIENT_PORT}`);
|
|
} else {
|
|
console.error(err);
|
|
}
|
|
});
|