diff --git a/package-lock.json b/package-lock.json index 4cb5170..cafd891 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@hellocoop/mockin", - "version": "3.1.1", + "version": "3.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@hellocoop/mockin", - "version": "3.1.1", + "version": "3.2.0", "license": "MIT", "dependencies": { "@fastify/cors": "^11.3.0", diff --git a/package.json b/package.json index 6b82fb1..d0e8d0e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hellocoop/mockin", "private": false, - "version": "3.1.1", + "version": "3.2.0", "description": "Hellō Mock Login OpenID Connect Server", "engines": { "node": ">=22" diff --git a/src/aauth/consent.js b/src/aauth/consent.js index 1e68960..7a8f0f1 100644 --- a/src/aauth/consent.js +++ b/src/aauth/consent.js @@ -1,4 +1,5 @@ // aauth/consent.js — GET /aauth/consent?code=…&callback=… +// GET /aauth/bounce/:code // // User-facing consent endpoint. The agent directs the user's browser here // after receiving requirement=interaction; mockin auto-approves the @@ -9,6 +10,7 @@ // navigation, not a signed agent call. The single-use `code` is the // authorization handle. +import { ISSUER } from '../config.js' import { getPendingByCode, updatePending } from './state.js' import { problem } from './problem.js' @@ -24,6 +26,34 @@ export const consent = async (req, reply) => { return problem(reply, 400, 'invalid_request', 'unknown code') } + // A connection is not the PS's to approve. The person has to link an + // account at the resource, so send them to the resource's own + // interaction_endpoint with the code the resource is holding, and a + // callback to bounce back to. The record terminates on that bounce — + // the resource finishing is the event, not the person arriving here. + if (entry.kind === 'connection') { + if (entry.status === 'approved') { + reply.header('Content-Type', 'text/html') + return reply.send( + '
You may close this window.
', + ) + } + const target = new URL(entry.interaction_endpoint) + target.searchParams.set('code', entry.interaction_code) + target.searchParams.set('callback', `${ISSUER}/aauth/bounce/${entry.code}`) + // A callback supplied here is where the AGENT wants the person to end + // up; remember it so the bounce can forward once the resource is done. + if (callback) { + try { + updatePending(entry.id, { agent_callback: new URL(callback).toString() }) + } catch { + return problem(reply, 400, 'invalid_request', 'invalid callback url') + } + } + return reply.redirect(target.toString()) + } + updatePending(entry.id, { status: 'approved' }) if (callback) { @@ -44,3 +74,49 @@ export const consent = async (req, reply) => { '