import express from "express"; import * as oidc from "oidc-provider"; import * as dotenv from "dotenv"; import { join, dirname } from "path"; import { fileURLToPath } from "url"; import Handlebars from "handlebars"; import assert from "assert"; import { readFileSync } from "fs"; dotenv.config({ quiet: true }); const AUTH_PROVIDER_PORT = Number(process.env.AUTH_PROVIDER_PORT) || 3000; const ACCOUNT_USERNAME = String(process.env.ACCOUNT_USERNAME) || "unknown_user"; const provider_login_page = Handlebars.compile( readFileSync(join(dirname(fileURLToPath(import.meta.url)), "provider.html"), { encoding: "utf-8", }), ); const provider = new oidc.Provider("http://localhost:3000", { features: { registration: { enabled: true, initialAccessToken: false, issueRegistrationAccessToken: true, }, devInteractions: { enabled: false, }, }, async findAccount(ctx, id) { assert(id == ACCOUNT_USERNAME); return { accountId: id, async claims(use, scope) { return { sub: id }; }, }; }, }); const app = express(); app.get("/interaction/:uid", async (req, res) => { const details = await provider.interactionDetails(req, res); const login = details.prompt.name == "login"; res.header("Content-Type", "text/html"); res.send(provider_login_page({ login, details: JSON.stringify(details, null, 3) })); }); app.post("/interaction/:uid", async (req, res) => { const interactionDetails = await provider.interactionDetails(req, res); const details = interactionDetails.prompt.details; let grant: oidc.Grant | undefined; if (interactionDetails.grantId) { grant = await provider.Grant.find(interactionDetails.grantId); } if (typeof grant == "undefined") { if ( interactionDetails.session && typeof interactionDetails.params.client_id == "string" ) { grant = new provider.Grant({ accountId: interactionDetails.session.accountId, clientId: interactionDetails.params.client_id, }); } } let consent: { consent: { grantId: string } } | {} = {}; if (typeof grant != "undefined") { if (details.missingOIDCScope instanceof Array) { grant.addOIDCScope(details.missingOIDCScope.join(" ")); } if (details.missingOIDCClaims instanceof Array) { grant.addOIDCClaims(details.missingOIDCClaims); } if (details.missingResourceScopes) { for (const [indicator, scopes] of Object.entries( details.missingResourceScopes, )) { grant.addResourceScope(indicator, scopes.join(" ")); } } const grantId = await grant.save(); if (interactionDetails.grantId != grantId) { consent = { consent: { grantId, }, }; } } const result = { login: { accountId: ACCOUNT_USERNAME, }, ...consent, }; const error = { error: "access_denied", }; return provider.interactionFinished(req, res, result); // result object below }); app.use(provider.callback()); app.listen(AUTH_PROVIDER_PORT, (err) => { if (!err) { console.log(`Auth provider listening on port ${AUTH_PROVIDER_PORT}`); } });