Add some rudimentary authentication to the API
This commit is contained in:
parent
7dc9d0a5cb
commit
4c4fffaf9d
3
.env.sample
Normal file
3
.env.sample
Normal file
@ -0,0 +1,3 @@
|
||||
USERNAME="a-username"
|
||||
PASSWORD="a-password"
|
||||
DB="db-name"
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
||||
/.phpunit.result.cache
|
||||
.idea
|
||||
/*.db
|
||||
.env
|
@ -19,6 +19,7 @@
|
||||
"symfony/dom-crawler": "^6.2",
|
||||
"symfony/css-selector": "^6.2",
|
||||
"symfony/http-client": "^6.2",
|
||||
"league/uri": "^6.8"
|
||||
"league/uri": "^6.8",
|
||||
"vlucas/phpdotenv": "^5.5"
|
||||
}
|
||||
}
|
||||
|
306
composer.lock
generated
306
composer.lock
generated
@ -4,8 +4,70 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "352975aee49cdf2b7ee0c1f9a6ced635",
|
||||
"content-hash": "f62bc46a34451432be93743680515868",
|
||||
"packages": [
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"version": "v1.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
||||
"reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831",
|
||||
"reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GrahamCampbell\\ResultType\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "An Implementation Of The Result Type",
|
||||
"keywords": [
|
||||
"Graham Campbell",
|
||||
"GrahamCampbell",
|
||||
"Result Type",
|
||||
"Result-Type",
|
||||
"result"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
|
||||
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-02-25T20:23:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/uri",
|
||||
"version": "6.8.0",
|
||||
@ -246,6 +308,81 @@
|
||||
},
|
||||
"time": "2022-08-18T16:18:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/schmittjoh/php-option.git",
|
||||
"reference": "dd3a383e599f49777d8b628dadbb90cae435b87e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e",
|
||||
"reference": "dd3a383e599f49777d8b628dadbb90cae435b87e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": true
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "1.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpOption\\": "src/PhpOption/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Johannes M. Schmitt",
|
||||
"email": "schmittjoh@gmail.com",
|
||||
"homepage": "https://github.com/schmittjoh"
|
||||
},
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "Option Type for PHP",
|
||||
"keywords": [
|
||||
"language",
|
||||
"option",
|
||||
"php",
|
||||
"type"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/schmittjoh/php-option/issues",
|
||||
"source": "https://github.com/schmittjoh/php-option/tree/1.9.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-02-25T19:38:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
"version": "2.0.2",
|
||||
@ -935,6 +1072,89 @@
|
||||
],
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
|
||||
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.27-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php80\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ion Bazan",
|
||||
"email": "ion.bazan@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-11-03T14:55:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
"version": "v3.2.1",
|
||||
@ -1019,6 +1239,90 @@
|
||||
}
|
||||
],
|
||||
"time": "2023-03-01T10:32:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
"version": "v5.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vlucas/phpdotenv.git",
|
||||
"reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7",
|
||||
"reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pcre": "*",
|
||||
"graham-campbell/result-type": "^1.0.2",
|
||||
"php": "^7.1.3 || ^8.0",
|
||||
"phpoption/phpoption": "^1.8",
|
||||
"symfony/polyfill-ctype": "^1.23",
|
||||
"symfony/polyfill-mbstring": "^1.23.1",
|
||||
"symfony/polyfill-php80": "^1.23.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.4.1",
|
||||
"ext-filter": "*",
|
||||
"phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-filter": "Required to use the boolean validator."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": true
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "5.5-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dotenv\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Vance Lucas",
|
||||
"email": "vance@vancelucas.com",
|
||||
"homepage": "https://github.com/vlucas"
|
||||
}
|
||||
],
|
||||
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
|
||||
"keywords": [
|
||||
"dotenv",
|
||||
"env",
|
||||
"environment"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-10-16T01:01:54+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
|
10
index.php
10
index.php
@ -2,6 +2,7 @@
|
||||
|
||||
require_once __DIR__ . "/vendor/autoload.php";
|
||||
|
||||
use Lewisdale\Webmentions\Api\WebmentionApi;
|
||||
use Lewisdale\Webmentions\Endpoint;
|
||||
use Lewisdale\Webmentions\Exceptions\InvalidSourceException;
|
||||
use Lewisdale\Webmentions\Exceptions\InvalidTargetException;
|
||||
@ -16,10 +17,13 @@ use Lewisdale\Webmentions\Router\StatusCode;
|
||||
use Lewisdale\Webmentions\Webmention;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
|
||||
$router = new Router();
|
||||
$gateway = new SqliteGateway("webmentions.db");
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||
$dotenv->load();
|
||||
|
||||
$apiController = new \Lewisdale\Webmentions\Api\WebmentionApi($gateway);
|
||||
$router = new Router();
|
||||
$gateway = new SqliteGateway($_ENV["DB"]);
|
||||
|
||||
$apiController = new WebmentionApi($gateway);
|
||||
|
||||
$router->post("/send", function (Request $req, Response $response) {
|
||||
$mentioner = new Webmention();
|
||||
|
@ -19,6 +19,11 @@ class WebmentionApi
|
||||
// List Webmentions. Optionally filter by target.
|
||||
public function list(Request $request, Response $response)
|
||||
{
|
||||
if (!$this->verifyUser($request)) {
|
||||
$response->status_code = StatusCode::Unauthorized;
|
||||
return "Unauthorized";
|
||||
}
|
||||
|
||||
$target = array_key_exists("post", $request->query) ? $request->query["post"] : null;
|
||||
$mentions = !empty($target) ? $this->gateway->getByPost($target) : $this->gateway->list();
|
||||
return $response->json($mentions);
|
||||
@ -27,6 +32,11 @@ class WebmentionApi
|
||||
// Get a webmention by ID
|
||||
public function get(Request $request, Response $response)
|
||||
{
|
||||
if (!$this->verifyUser($request)) {
|
||||
$response->status_code = StatusCode::Unauthorized;
|
||||
return "Unauthorized";
|
||||
}
|
||||
|
||||
$id = (int)$request->params[0];
|
||||
|
||||
if ($id) {
|
||||
@ -40,4 +50,25 @@ class WebmentionApi
|
||||
|
||||
$response->status_code = StatusCode::NotFound;
|
||||
}
|
||||
|
||||
private function verifyUser(Request $request): bool
|
||||
{
|
||||
if (!array_key_exists("authorization", $request->headers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[, $auth] = explode("Basic ", $request->headers["authorization"]);
|
||||
|
||||
if (empty($auth)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
[$username, $password] = explode(":", base64_decode($auth));
|
||||
|
||||
if (!empty($username) && $username === $_ENV["USERNAME"] && !empty($password) && $password === $_ENV["PASSWORD"]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -2,14 +2,18 @@
|
||||
|
||||
namespace Lewisdale\Webmentions\Router;
|
||||
|
||||
class Request {
|
||||
class Request
|
||||
{
|
||||
public function __construct(
|
||||
public array $params,
|
||||
public array $params,
|
||||
public readonly string $uri,
|
||||
public readonly Method $method,
|
||||
public readonly array $post = [],
|
||||
public readonly array $query = [],
|
||||
) {}
|
||||
public readonly array $post = [],
|
||||
public readonly array $query = [],
|
||||
public readonly array $headers = [],
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -29,37 +29,46 @@ use Exception;
|
||||
*
|
||||
* But if you're reading this, it's already too late.
|
||||
*/
|
||||
class Router {
|
||||
class Router
|
||||
{
|
||||
private array $routes;
|
||||
|
||||
private function trim_route(string $route): string {
|
||||
private function trim_route(string $route): string
|
||||
{
|
||||
$route = $route == "/" ? $route : rtrim($route, "/");
|
||||
$route = str_replace("/", "\/", $route);
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
private function format_route(string $route): string {
|
||||
private function format_route(string $route): string
|
||||
{
|
||||
$formatted = $this->trim_route($route);
|
||||
return "#^$formatted$#";
|
||||
}
|
||||
|
||||
public function get(string $route, callable $fn) {
|
||||
public function get(string $route, callable $fn)
|
||||
{
|
||||
$this->routes[] = new Route(Method::GET, $this->format_route($route), $fn);
|
||||
}
|
||||
|
||||
public function post(string $route, callable $fn) {
|
||||
public function post(string $route, callable $fn)
|
||||
{
|
||||
$this->routes[] = new Route(Method::POST, $this->format_route($route), $fn);
|
||||
}
|
||||
|
||||
private function get_params(array $matches): array {
|
||||
private function get_params(array $matches): array
|
||||
{
|
||||
return array_map(
|
||||
function($match) { return $match[0]; },
|
||||
function ($match) {
|
||||
return $match[0];
|
||||
},
|
||||
array_shift($matches)
|
||||
);
|
||||
}
|
||||
|
||||
public function dispatch() {
|
||||
public function dispatch()
|
||||
{
|
||||
$uri = $_SERVER['REQUEST_URI'];
|
||||
$method = Method::from($_SERVER['REQUEST_METHOD']);
|
||||
|
||||
@ -67,9 +76,9 @@ class Router {
|
||||
|
||||
$response = new Response();
|
||||
|
||||
foreach($this->routes as $route) {
|
||||
foreach ($this->routes as $route) {
|
||||
if ($method == $route->type) {
|
||||
$matches = array();
|
||||
$matches = [];
|
||||
if (preg_match_all($route->pattern, $uri, $matches)) {
|
||||
$num_matched++;
|
||||
$fn = $route->fn;
|
||||
@ -94,7 +103,8 @@ class Router {
|
||||
}
|
||||
|
||||
|
||||
private function respond(Response $response) {
|
||||
private function respond(Response $response)
|
||||
{
|
||||
http_response_code($response->status_code->code());
|
||||
|
||||
echo $response->body;
|
||||
@ -107,7 +117,7 @@ class Router {
|
||||
$query = $_GET;
|
||||
$post = $_POST;
|
||||
|
||||
return new Request($params, $uri, $method, $post, $query);
|
||||
return new Request($params, $uri, $method, $post, $query, array_change_key_case(getallheaders(), CASE_LOWER));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,22 +2,27 @@
|
||||
|
||||
namespace Lewisdale\Webmentions\Router;
|
||||
|
||||
enum StatusCode {
|
||||
enum StatusCode
|
||||
{
|
||||
case NotFound;
|
||||
case Ok;
|
||||
case Redirect;
|
||||
case InternalError;
|
||||
case BadRequest;
|
||||
case Created;
|
||||
case Forbidden;
|
||||
case Unauthorized;
|
||||
|
||||
public function code() : int
|
||||
public function code(): int
|
||||
{
|
||||
return match($this) {
|
||||
return match ($this) {
|
||||
StatusCode::NotFound => 404,
|
||||
StatusCode::Ok => 200,
|
||||
StatusCode::InternalError => 500,
|
||||
StatusCode::Redirect => 301,
|
||||
StatusCode::BadRequest => 400,
|
||||
StatusCode::Unauthorized => 401,
|
||||
StatusCode::Forbidden => 403,
|
||||
StatusCode::Created => 201,
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user