Initial commit
This commit is contained in:
commit
7812d56d02
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
config.php
|
1
.htaccess
Normal file
1
.htaccess
Normal file
|
@ -0,0 +1 @@
|
||||||
|
PerlHeaderParserHandler Lemonldap::NG::Handler::ApacheMP2
|
33
form.php
Normal file
33
form.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>IndieAuth</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/home.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>IndieAuth</h1>
|
||||||
|
<?php if ( ! is_null( $error ) ) : ?>
|
||||||
|
<h2 class="centre">Error</h2>
|
||||||
|
<p class="centre"><?php echo $error; ?></p>
|
||||||
|
<?php else : ?>
|
||||||
|
<h2 class="centre">Authenticate</h2>
|
||||||
|
<form method="POST" action="" class="centre">
|
||||||
|
<p>You are about to login with client <pre><?php echo htmlspecialchars($client_id); ?></pre></p>
|
||||||
|
<?php if ( strlen($scope) > 0) : ?>
|
||||||
|
<p>With the following scopes</p>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Scopes</legend>
|
||||||
|
<?php foreach (explode(' ', $scope) as $n => $checkbox) : ?>
|
||||||
|
<div>
|
||||||
|
<input id="scope_<?php echo $n; ?>" type="checkbox" name="scopes[]" value="<?php echo htmlspecialchars($checkbox); ?>" checked />
|
||||||
|
<label for="scope_<?php echo $n; ?>"><?php echo $checkbox; ?></label>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</fieldset>
|
||||||
|
<?php endif; ?>
|
||||||
|
<p>After login you will be redirected to <pre><?php echo htmlspecialchars($redirect_uri); ?></pre></p>
|
||||||
|
<input type="hidden" name="_csrf" value="<?php echo $csrf_code; ?>" />
|
||||||
|
<input class="submit" type="submit" name="submit" value="Submit" />
|
||||||
|
</form>
|
||||||
|
<?php endif ?>
|
||||||
|
</body>
|
80
functions.php
Normal file
80
functions.php
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
function create_signed_code(
|
||||||
|
$key,
|
||||||
|
$message,
|
||||||
|
$ttl = 31536000,
|
||||||
|
$appended_data = ""
|
||||||
|
) {
|
||||||
|
$expires = time() + $ttl;
|
||||||
|
$body = $message . $expires . $appended_data;
|
||||||
|
$signature = hash_hmac("sha256", $body, $key);
|
||||||
|
return dechex($expires) .
|
||||||
|
":" .
|
||||||
|
$signature .
|
||||||
|
":" .
|
||||||
|
base64_url_encode($appended_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function verify_signed_code($key, $message, $code) {
|
||||||
|
$code_parts = explode(":", $code, 3);
|
||||||
|
if (count($code_parts) !== 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$expires = hexdec($code_parts[0]);
|
||||||
|
if (time() > $expires) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$body = $message . $expires . base64_url_decode($code_parts[2]);
|
||||||
|
$signature = hash_hmac("sha256", $body, $key);
|
||||||
|
return hash_equals($signature, $code_parts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filter_input_regexp($type, $variable, $regexp, $flags = null) {
|
||||||
|
$options = [
|
||||||
|
"options" => ["regexp" => $regexp],
|
||||||
|
];
|
||||||
|
if ($flags !== null) {
|
||||||
|
$options["flags"] = $flags;
|
||||||
|
}
|
||||||
|
return filter_input($type, $variable, FILTER_VALIDATE_REGEXP, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_q_value($mime, $accept) {
|
||||||
|
$fulltype = preg_replace('@^([^/]+\/).+$@', '$1*', $mime);
|
||||||
|
$regex = implode("", [
|
||||||
|
"/(?<=^|,)\s*(\*\/\*|",
|
||||||
|
preg_quote($fulltype, "/"),
|
||||||
|
"|",
|
||||||
|
preg_quote($mime, "/"),
|
||||||
|
')\s*(?:[^,]*?;\s*q\s*=\s*([0-9.]+))?\s*(?:,|$)/',
|
||||||
|
]);
|
||||||
|
$out = preg_match_all($regex, $accept, $matches);
|
||||||
|
$types = array_combine($matches[1], $matches[2]);
|
||||||
|
if (array_key_exists($mime, $types)) {
|
||||||
|
$q = $types[$mime];
|
||||||
|
} elseif (array_key_exists($fulltype, $types)) {
|
||||||
|
$q = $types[$fulltype];
|
||||||
|
} elseif (array_key_exists("*/*", $types)) {
|
||||||
|
$q = $types["*/*"];
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return $q === "" ? 1 : floatval($q);
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64_url_encode($string) {
|
||||||
|
$string = base64_encode($string);
|
||||||
|
$string = rtrim($string, "=");
|
||||||
|
$string = strtr($string, "+/", "-_");
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64_url_decode($string) {
|
||||||
|
$string = strtr($string, "-_", "+/");
|
||||||
|
$padding = strlen($string) % 4;
|
||||||
|
if ($padding !== 0) {
|
||||||
|
$string .= str_repeat("=", 4 - $padding);
|
||||||
|
}
|
||||||
|
$string = base64_decode($string);
|
||||||
|
return $string;
|
||||||
|
}
|
132
index.php
Normal file
132
index.php
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
<?php
|
||||||
|
/* Single user IndieAuth endpoint using server envvars from SSO
|
||||||
|
* Based on https://github.com/inklings-io/selfauth
|
||||||
|
*/
|
||||||
|
require_once 'functions.php';
|
||||||
|
require_once 'config.php';
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
|
||||||
|
// Verification of codes.
|
||||||
|
$code = filter_input_regexp( INPUT_POST, 'code', '@^[0-9a-f]+:[0-9a-f]{64}:@' );
|
||||||
|
if ( ! is_null($code) ){
|
||||||
|
$redirect_uri = filter_input( INPUT_POST, 'redirect_uri', FILTER_VALIDATE_URL );
|
||||||
|
$client_id = filter_input( INPUT_POST, 'client_id', FILTER_VALIDATE_URL );
|
||||||
|
|
||||||
|
if ( ! (
|
||||||
|
is_string($code) &&
|
||||||
|
is_string($redirect_uri) &&
|
||||||
|
is_string($client_id) &&
|
||||||
|
verify_signed_code( APP_KEY, USER_URL . $redirect_uri . $client_id, $code )
|
||||||
|
) ) {
|
||||||
|
$error = 'Invalid code';
|
||||||
|
http_response_code(400);
|
||||||
|
include 'form.php';
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = array('me' => USER_URL);
|
||||||
|
$code_parts = explode(':', $code, 3);
|
||||||
|
$accept_header = $_SERVER['HTTP_ACCEPT'] ?: '*/*';
|
||||||
|
|
||||||
|
if ( '' !== $code_parts[2] ) {
|
||||||
|
$response['scope'] = base64_url_decode($code_parts[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = get_q_value('application/json', $accept_header);
|
||||||
|
$form = get_q_value('application/x-www-form-urlencoded', $accept_header);
|
||||||
|
|
||||||
|
if ( 0 === $json && 0 === $form ){
|
||||||
|
$error = 'Client does not accept JSON or Form encoded responses';
|
||||||
|
http_response_code(406);
|
||||||
|
include 'form.php';
|
||||||
|
die();
|
||||||
|
} elseif ( $json >= $form ){
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
exit( json_encode($response) );
|
||||||
|
} else {
|
||||||
|
header('Content-Type: application/x-www-form-urlencoded');
|
||||||
|
exit( http_build_query($response) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No code submitted,
|
||||||
|
// Check login
|
||||||
|
if ( is_null($_SERVER["REMOTE_USER"]) ) {
|
||||||
|
$error = 'Not logged in. Login on the <a href="https://auth.cool110.xyz/">SSO portal</a>';
|
||||||
|
http_response_code(403);
|
||||||
|
include 'form.php';
|
||||||
|
die();
|
||||||
|
} elseif ( USER_NAME !== $_SERVER["REMOTE_USER"] ){
|
||||||
|
$error = 'This system is for ' . USER_NAME . ' only.';
|
||||||
|
http_response_code(403);
|
||||||
|
include 'form.php';
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
// Filter client data
|
||||||
|
$me = filter_input( INPUT_GET, 'me', FILTER_VALIDATE_URL );
|
||||||
|
$client_id = filter_input( INPUT_GET, 'client_id', FILTER_VALIDATE_URL );
|
||||||
|
$redirect_uri = filter_input( INPUT_GET, 'redirect_uri', FILTER_VALIDATE_URL );
|
||||||
|
$state = filter_input_regexp( INPUT_GET, 'state', '@^[\x20-\x7E]*$@' );
|
||||||
|
$response_type = filter_input_regexp( INPUT_GET, 'response_type', '@^(id|code)?$@' );
|
||||||
|
$scope = filter_input_regexp( INPUT_GET, 'scope', '@^([\x21\x23-\x5B\x5D-\x7E]+( [\x21\x23-\x5B\x5D-\x7E]+)*)?$@' );
|
||||||
|
|
||||||
|
if ( ! is_string($client_id) ) {
|
||||||
|
$error .= 'Invalid Client ID<br />';
|
||||||
|
}
|
||||||
|
if ( ! is_string($redirect_uri) ){
|
||||||
|
$error .= 'Invalid redirect URI<br />';
|
||||||
|
}
|
||||||
|
if ( false === $state ) {
|
||||||
|
$error .= 'Invalid state<br />';
|
||||||
|
}
|
||||||
|
if ( false === $response_type ) {
|
||||||
|
$error .= 'Invalid response type<br />';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $error ){
|
||||||
|
http_response_code(400);
|
||||||
|
include 'form.php';
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
$csrf_code = filter_input( INPUT_POST, '_csrf', FILTER_UNSAFE_RAW );
|
||||||
|
// If form submitted
|
||||||
|
if ( ! is_null($csrf_code) ) {
|
||||||
|
if ( !verify_signed_code( APP_KEY, $client_id . $redirect_uri . $state, $csrf_code ) ){
|
||||||
|
$error = 'Invalid CSFR code';
|
||||||
|
http_response_code(400);
|
||||||
|
include 'form.php';
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope = filter_input_regexp( INPUT_POST, 'scopes', '@^[\x21\x23-\x5B\x5D-\x7E]+$@', FILTER_REQUIRE_ARRAY );
|
||||||
|
if ( ! is_null($scope) ) {
|
||||||
|
if ( false === $scope || in_array( false, $scope, true ) ) {
|
||||||
|
$error = 'Provided scopes contain illegal characters';
|
||||||
|
}
|
||||||
|
$scope = implode( ' ', $scope );
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = create_signed_code( APP_KEY, USER_URL . $redirect_uri . $client_id, 5 * 60, $scope );
|
||||||
|
|
||||||
|
$final_redir = $redirect_uri;
|
||||||
|
if ( strpos($redirect_uri, '?') === false ) {
|
||||||
|
$final_redir .= '?';
|
||||||
|
} else {
|
||||||
|
$final_redir .= '&';
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameters = array(
|
||||||
|
'code' => $code,
|
||||||
|
'me' => USER_URL
|
||||||
|
);
|
||||||
|
if ( ! is_null($state) ) {
|
||||||
|
$parameters['state'] = $state;
|
||||||
|
}
|
||||||
|
$final_redir .= http_build_query($parameters);
|
||||||
|
header('Location: ' . $final_redir, true, 302);
|
||||||
|
} else {
|
||||||
|
$csrf_code = create_signed_code(APP_KEY, $client_id . $redirect_uri . $state, 2 * 60);
|
||||||
|
include 'form.php';
|
||||||
|
}
|
Loading…
Reference in a new issue