getRequestTarget())['path'] ?? ""; if (!strlen($id) || strpos($id, "/") !== false) { return new EmptyResponse(404); } $id = rawurldecode($id); // gather the query parameters and act on the "proc" parameter $method = "do".ucfirst(strtolower($req->getQueryParams()['proc'] ?? "discovery")); if (!method_exists($this, $method)) { return new EmptyResponse(404); } else { return $this->$method($id, $req); } } /** Produces a user-identifier URL consiustent with the request * * This involves reconstructing the scheme and authority based on $_SERVER * variables; it may fail depending on server configuration */ protected function buildIdentifier(ServerRequestInterface $req, bool $baseOnly = false): string { // construct the base user identifier URL; the user is never checked against the database $s = $req->getServerParams(); $path = $req->getRequestTarget()['path']; $https = (strlen($s['HTTPS'] ?? "") && $s['HTTPS'] !== "off"); $port = (int) ($s['SERVER_PORT'] ?? 0); $port = (!$port || ($https && $port == 443) || (!$https && $port == 80)) ? "" : ":$port"; $base = URL::normalize(($https ? "https" : "http")."://".$s['HTTP_HOST'].$port."/"); return !$baseOnly ? URL::normalize($base.$path) : $base; } /** Presents a very basic user profile for discovery purposes * * The HTML document itself consists only of link elements and an * encoding declaration; Link header-fields are also included for * HEAD requests * * Since discovery is publicly accessible, we produce a discovery * page for all potential user name so as not to facilitate user * enumeration */ protected function doDiscovery(string $user, ServerRequestInterface $req): ResponseInterface { $base = $this->buildIdentifier($req, true); $id = $this->buildIdentifier($req); $urlAuth = $id."?proc=login"; $urlToken = $id."?proc=issue"; $urlService = $base."microsub"; // output an extremely basic identity resource $html = ''; return new Response($html, 200, [ "Link: <$urlAuth>; rel=\"authorization_endpoint\"", "Link: <$urlToken>; rel=\"token_endpoint\"", "Link: <$urlService>; rel=\"microsub\"", ]); } /** Handles the authentication/authorization process * * Authentication is achieved via an HTTP Basic authentiation * challenge; once the user successfully logs in a code is issued * and redirection occurs. Scopes are for all intents and purposes * ignored and client information is not presented. */ protected function doLogin(string $user, ServerRequestInterface $req): ResponseInterface { if (!$req->getAttribute("authenticated", false)) { // user has not yet logged in, or has failed to log in return new EmptyResponse(401); } else { // user has logged in // ensure the logged-in user matches the IndieAuth identifier URL $id = $req->getAttribute("authenticatedUser"); $query = $req->getQueryParams(); $url = buildIdentifier($req); if ($user !== $id || URL::normalize($query['me']) !== $url) { return new EmptyResponse(403); } else { $redir = URL::normalize(rawurldecode($query['redirect_uri'])); $state = $query['state'] ?? ""; // check that the redirect URL is an absolute one if (!URL::absolute($redir)) { return new EmptyResponse(400); } // issue an authorization code and build the redirect URL $code = Arsse::$db->tokenCreate($id, "microsub.auth", null, Date::add("PT2M"), $query['client_id']); $next = URL::queryAppend($redir, "code=$code&state=$state"); return new EmptyResponse(302, ["Location: $next"]); } } } protected function doIssue(string $user, ServerRequestInterface $req): ResponseInterface { } }