UNPKG

@indiekit/endpoint-auth

Version:

IndieAuth authentication and authorization endpoint for Indiekit. Grants and verifies access tokens and authenticates users.

108 lines (94 loc) 3.14 kB
import process from "node:process"; import { IndiekitError } from "@indiekit/error"; import { validationResult } from "express-validator"; import { getRequestUriData } from "../pushed-authorization-request.js"; import { getScopeItems } from "../scope.js"; import { signToken } from "../token.js"; export const consentController = { /** * View consent form * @type {import("express").RequestHandler} */ get(request, response) { if (!request.query.request_uri) { throw IndiekitError.badRequest( response.locals.__("BadRequestError.missingParameter", "request_uri"), ); } try { const { me, redirect_uri, scope } = getRequestUriData(request); const authType = scope === undefined ? "authenticate" : "authorize"; if (process.env.PASSWORD_SECRET) { response.render("consent", { title: response.locals.__(`auth.consent.${authType}.title`), authType, me, redirect_uri, scopeItems: getScopeItems(scope, response), }); } else { response.redirect(request.baseUrl + "/new-password?setup=true"); } } catch { throw IndiekitError.badRequest( response.locals.__("BadRequestError.invalidValue", "request_uri"), ); } }, /** * Submit consent form * @type {import("express").RequestHandler} * @see {@link https://indieauth.spec.indieweb.org/#authorization-response} */ post(request, response) { const { application } = request.app.locals; let scope = request.body?.scope; const { client_id, code_challenge, code_challenge_method, me, redirect_uri, state, } = getRequestUriData(request); const authType = scope === undefined ? "authenticate" : "authorize"; const errors = validationResult(request); if (!errors.isEmpty()) { return response.status(422).render("consent", { title: response.locals.__(`auth.consent.${authType}.title`), authType, errors: errors.mapped(), me, redirect_uri, scopeItems: getScopeItems(scope, response), }); } // Scopes should be a space separated list if (scope) { scope = Array.isArray(scope) ? scope : [scope]; scope = scope.join(" "); } // Create authorization code const code = signToken({ client_id, ...(code_challenge && { code_challenge }), ...(code_challenge_method && { code_challenge_method }), jti: crypto.randomUUID(), me, redirect_uri, ...(scope && { scope }), }); // Authorization response const redirect = new URL(redirect_uri); redirect.searchParams.set("code", code); redirect.searchParams.set("iss", application.url); redirect.searchParams.set("state", state); // If client sent optional `me` value in initial authorization request, // add it to response. This is for backwards compatibility with clients // conforming to older versions of the IndieAuth spec. if (me) { redirect.searchParams.set("me", me); } response.redirect(redirect.href); }, };