Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

anniemaybytes/chihaya #60

Open
wants to merge 40 commits into
base: production
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0006bdc
empty
pjc09h Apr 11, 2023
478cb5f
Merge branch 'production' of github.com:biotorrents/gazelle into chihaya
pjc09h Apr 12, 2023
1000108
Merge branch 'production' of github.com:biotorrents/gazelle into chihaya
pjc09h May 12, 2023
7e1c3ee
login query hotfix
pjc09h May 17, 2023
fc023a0
update composer
pjc09h May 19, 2023
cd2cb37
Merge branch 'production' of github.com:biotorrents/gazelle into crea…
pjc09h May 20, 2023
2e62b13
silly laravel
pjc09h May 20, 2023
e2c8a7f
long-forlorn api
pjc09h May 20, 2023
0c0d036
improve binary handling a bit
pjc09h May 20, 2023
032d30a
upsert
pjc09h May 20, 2023
abd9cdb
it works
pjc09h May 20, 2023
a222f88
pseudo orm
pjc09h May 20, 2023
e60b07b
put the cache key algorithm in one place
pjc09h May 20, 2023
153a0b9
fix the internal api
pjc09h May 20, 2023
f995aba
bearer token scopes ground work
pjc09h May 20, 2023
3d549c1
cleanup
pjc09h May 20, 2023
a22a3d3
refactor, but there's a bug in token validation
pjc09h May 20, 2023
960267c
various optimizations
pjc09h May 20, 2023
0b17ee0
improve token scopes
pjc09h May 20, 2023
70ceeb7
start implementing a real api response spec
pjc09h May 20, 2023
66f1fbf
use application/vnd.api+json
pjc09h May 20, 2023
dccc822
add openapi.json
pjc09h May 20, 2023
68583c8
start fleshing out an openapi spec
pjc09h May 20, 2023
d1d6023
Merge branch 'creatorObjects' of github.com:biotorrents/gazelle into …
pjc09h May 20, 2023
7ad2e8e
openapi stuff
pjc09h May 20, 2023
846eafb
base responses
pjc09h May 20, 2023
47cb1b9
start crudding stuff up for real
pjc09h May 21, 2023
9d43886
clean out the old api
pjc09h May 21, 2023
9d922d0
mock up some more crud stuff
pjc09h May 21, 2023
481c28e
more crud scaffolding
pjc09h May 21, 2023
f65896d
empty commit
pjc09h May 28, 2023
b15f178
Merge branch 'development' of github.com:biotorrents/gazelle into chi…
pjc09h May 28, 2023
12f059e
add schema file
pjc09h May 28, 2023
70c1d3a
draft migration
pjc09h May 28, 2023
8c87cc3
fix
pjc09h May 29, 2023
5089c35
now to fix all the inevitable bugs
pjc09h May 29, 2023
34307c3
fix client whitelist
pjc09h May 29, 2023
2447424
fix the misc values thing
pjc09h May 29, 2023
1b1057c
"fix" the service stats
pjc09h May 29, 2023
5508efe
more little deletions
pjc09h May 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 159 additions & 108 deletions app/API/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,149 +15,222 @@

class Base
{
private static $source = null;
private static $version = 1;
# https://jsonapi.org/format/#document-jsonapi-object
private static $version = "1.1";


/**
* checkToken
* validateBearerToken
*
* Validates an authorization header and API token.
*
* @return ?array
*/
public static function checkToken(int $userId, string $token = ""): void
public static function validateBearerToken(): ?array
{
$app = \Gazelle\App::go();

/** */

# get the token off the headers
if (empty($token)) {
# escape bearer token
$server = \Http::request("server");
# escape bearer token
$server = \Http::request("server");

# no header present
if (empty($server["HTTP_AUTHORIZATION"])) {
self::failure(401, "no authorization header present");
}
# no header present
if (empty($server["HTTP_AUTHORIZATION"])) {
self::failure(401, "no authorization header present");
}

# https://tools.ietf.org/html/rfc6750
$authorizationHeader = explode(" ", $server["HTTP_AUTHORIZATION"]);
# https://tools.ietf.org/html/rfc6750
if (!preg_match("/^Bearer\s+(.+)$/", $server["HTTP_AUTHORIZATION"], $matches)) {
self::failure(401, "invalid authorization header format");
}

# too much whitespace
if (count($authorizationHeader) !== 2) {
self::failure(401, "token must be given as \"Authorization: Bearer {\$token}\"");
}
# we have a token!
$token = $matches[1];

# not rfc compliant
if ($authorizationHeader[0] !== "Bearer") {
self::failure(401, "token must be given as \"Authorization: Bearer {\$token}\"");
}
# empty token
if (empty($token)) {
self::failure(401, "empty token provided");
}

# we have a token!
$token = $authorizationHeader[1];
/** */

# empty token
if (empty($token)) {
self::failure(401, "empty token provided");
# check the database
$query = "select id, userId, token from api_tokens use index (userId_token) where deleted_at is null";
$ref = $app->dbNew->multi($query, []);

foreach ($ref as $row) {
$good = password_verify($token, $row["token"]);
if ($good) {
/*
# is the user disabled?
if (\User::isDisabled($row["userId"])) {
self::failure(401, "user disabled");
}
*/

# return the data
return $row;
}
} # if (empty($token))
}

# default failure
self::failure(401, "invalid token");
}


/**
* validateFrontendHash
*
* Checks a frontend key against a backend one.
* The key is hash(sessionId . siteApiSecret).
*/
public static function validateFrontendHash(): void
{
$app = \Gazelle\App::go();

/** */

# check the database
$query = "select userId, token, revoked from api_user_tokens where UserID = ?";
$row = $app->dbNew->row($query, [$userId]);
#~d($row);exit;
# escape bearer token
$server = \Http::request("server");

if (!$row) {
self::failure(401, "token not found");
# no header present
if (empty($server["HTTP_AUTHORIZATION"])) {
self::failure(401, "no authorization header present");
}

# user revoked the token
if (intval($row["revoked"]) === 1) {
self::failure(401, "token revoked");
# https://tools.ietf.org/html/rfc6750
if (!preg_match("/^Bearer\s+(.+)$/", $server["HTTP_AUTHORIZATION"], $matches)) {
self::failure(401, "invalid authorization header format");
}

# user doesn't own that token
if ($userId !== intval($row["userId"])) {
self::failure(401, "token user mismatch");
}
# we have a token!
$token = $matches[1];

/*
# user is disabled
if (\User::isDisabled($userId)) {
self::failure(401, "user disabled");
# empty token
if (empty($token)) {
self::failure(401, "empty token provided");
}
*/

# wrong token provided
if (!password_verify($token, $row["token"])) {
self::failure(401, "wrong token provided");
/** */

$query = "select sessionId from users_sessions where userId = ? order by expires desc limit 10";
$ref = $app->dbNew->multi($query, [ $app->user->core["id"] ]);

foreach ($ref as $row) {
$backendKey = implode(".", [$row["sessionId"], $app->env->getPriv("siteApiSecret")]);
$good = password_verify($backendKey, $token);

if ($good) {
return;
}
}

# default failure
self::failure(401, "invalid token");
}


/** token permissions */


/**
* success
*
* @see https://jsonapi.org/examples/
*/
public static function success(array|string $response): void
* validatePermissions
*
* Checks a token's permissions against a list of required permissions.
*/
public static function validatePermissions(int $tokenId, array $permissions = []): void
{
$app = \Gazelle\App::go();

if (empty($response)) {
self::failure(500, "the server provided no payload");
# quick sanity check
$permissions = array_map("strtolower", $permissions);
$allowedPermissions = ["create", "read", "update", "delete"];

# check that all permissions are valid
if (array_intersect($permissions, $allowedPermissions) !== $permissions) {
self::failure(401, "invalid permission");
}

\Http::response(200);
header("Content-Type: application/json; charset=utf-8");
print json_encode(
[
"id" => uniqid(),
"code" => 200,
# check the token's permissions
$query = "select permissions from api_tokens where id = ?";
$ref = $app->dbNew->single($query, [$tokenId]);

"data" => $response,
if (empty($ref)) {
self::failure(401, "no permissions found");
}

"meta" => [
"info" => self::info(),
"debug" => self::debug(),
],
# check that all required permissions are present
$tokenPermissions = json_decode($ref, true);
if (array_intersect($permissions, $tokenPermissions) !== $permissions) {
self::failure(401, "missing required permissions");
}
}


/** responses */


/**
* success
*
* @param $response HTTP success code (usually 2xx)
* @param $data the data set in the JSON response
*
* @see https://jsonapi.org/format/#document-structure
*/
public static function success(int $code = 200, $data = []): void
{
$response = [
"data" => $data,

"jsonapi" => [
"version" => self::$version,
],

"meta" => [
"id" => uniqid(),
"count" => (is_array($data) ? count($data) : 1),
#"debug" => self::debug(),
],
);
];

http_response_code($code);
header("Content-Type: application/vnd.api+json; charset=utf-8");

echo json_encode($response);
exit;
}


/**
* failure
*
* General failure routine for when bad things happen.
* @param $response HTTP error code (usually 4xx)
* @param string $data the error set in the JSON response
*
* @param string $message The error set in the JSON response
* @param $response HTTP error code (usually 4xx client errors)
*
* @see https://jsonapi.org/format/#error-objects
* @see https://jsonapi.org/format/#errors
*/
public static function failure(int $code = 400, array|string $response = "bad request"): void
public static function failure(int $code = 400, $data = "bad request"): void
{
\Http::response($code);
header("Content-Type: application/json; charset=utf-8");
print json_encode(
[
"id" => uniqid(),
"code" => $code,
$response = [
"errors" => $data,

"data" => $response,
"jsonapi" => [
"version" => self::$version,
],

"meta" => [
"info" => self::info(),
"debug" => self::debug(),
],
"meta" => [
"id" => uniqid(),
"count" => (is_array($data) ? count($data) : 1),
#"debug" => self::debug(),
],
);
];

http_response_code($code);
header("Content-Type: application/vnd.api+json; charset=utf-8");

echo json_encode($response);
exit;
}

Expand Down Expand Up @@ -185,26 +258,4 @@ private static function debug()
}
*/
}


/**
* info
*/
private static function info()
{
$app = \Gazelle\App::go();

return [
"source" => $app->env->siteName,
"version" => self::$version,
];
}


/**
* selfTest
*/
public static function selfTest()
{
}
} # class
Loading