initial commit
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
use Preauth\Env;
|
||||
global $auth;
|
||||
$env = new Env();
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><?php echo $env->getTitle(); ?></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Begin Icons -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="https://<?php echo $auth->getBaseDomain(); ?>/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="https://<?php echo $auth->getBaseDomain(); ?>/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="https://<?php echo $auth->getBaseDomain(); ?>/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="https://<?php echo $auth->getBaseDomain(); ?>/favicon-16x16.png">
|
||||
<link rel="manifest" href="https://<?php echo $auth->getBaseDomain(); ?>/site.webmanifest">
|
||||
<meta name="apple-mobile-web-app-title" content="<?php echo $env->getTitle(); ?>">
|
||||
<meta name="application-name" content="<?php echo $env->getTitle(); ?>">
|
||||
<meta name="msapplication-TileColor" content="<?php echo $env->getColor(); ?>">
|
||||
<meta name="theme-color" content="<?php echo $env->getColor(); ?>">
|
||||
<!-- End Icons -->
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0.25em;
|
||||
}
|
||||
html {
|
||||
background-color: <?php echo $env->getColor(); ?>;
|
||||
color: <?php echo $env->getTextColor(); ?>;
|
||||
display: table;
|
||||
font-family: sans-serif;
|
||||
font-size: 1.5em;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
body {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
font-weight: normal;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1><?php echo $env->getTitle(); ?></h1>
|
||||
<h2>Internal Server Error</h2>
|
||||
<p>Review the logs for more details.</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
# allow the pre-auth page to work
|
||||
preauth.example.com {
|
||||
reverse_proxy preauth:9000 {
|
||||
header_up X-Forwarded-Uri {uri}
|
||||
header_down -X-Powered-By
|
||||
rewrite /preauth.php
|
||||
transport fastcgi {
|
||||
root /preauth/
|
||||
split .php
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# if using caddy v2.9.x+ you can use this snippet
|
||||
# snippet to put the pre-auth system in front any service easily
|
||||
(preauth) {
|
||||
reverse_proxy {args[0]} preauth:9000 {
|
||||
header_up X-Forwarded-Uri {uri}
|
||||
header_down -X-Powered-By
|
||||
rewrite /preauth.php
|
||||
transport fastcgi {
|
||||
root /preauth/
|
||||
split .php
|
||||
}
|
||||
@preauth_ok status 2xx
|
||||
handle_response @preauth_ok {
|
||||
copy_response_headers {
|
||||
include Set-Cookie Location
|
||||
}
|
||||
{block}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# snippet usage: this will cause pre-auth to restrict access to https://protected.example.com/secure
|
||||
protected.example.com {
|
||||
import preauth /secure {
|
||||
reverse_proxy protected-service:9000
|
||||
}
|
||||
reverse_proxy exposed-service:9000
|
||||
}
|
||||
|
||||
## alternatively, if using an older version of caddy,
|
||||
## you will have to have add the boilerplate in your block
|
||||
#protected.example.com {
|
||||
# reverse_proxy /secure preauth:9000 {
|
||||
# header_up X-Forwarded-Uri {uri}
|
||||
# header_down -X-Powered-By
|
||||
# rewrite /preauth.php
|
||||
# transport fastcgi {
|
||||
# root /preauth/
|
||||
# split .php
|
||||
# }
|
||||
# @preauth_ok status 2xx
|
||||
# handle_response @preauth_ok {
|
||||
# copy_response_headers {
|
||||
# include Set-Cookie Location
|
||||
# }
|
||||
# reverse_proxy protected-service:9000
|
||||
# }
|
||||
# }
|
||||
# reverse_proxy exposed-service:9000
|
||||
#}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
FROM php:8.4-fpm-alpine
|
||||
|
||||
# get compose so we can install our php dependencies
|
||||
COPY --from=composer /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# app is stored here
|
||||
RUN mkdir /preauth
|
||||
WORKDIR /preauth
|
||||
COPY . /preauth/
|
||||
|
||||
# sessions are stored here
|
||||
RUN mkdir -p /tmp/sessions
|
||||
|
||||
# fcgi command for the healthcheck
|
||||
RUN apk add fcgi
|
||||
|
||||
# install our php dependencies
|
||||
RUN composer install
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
HEALTHCHECK --interval=60s --retries=3 --start-interval=1s --start-period=10s --timeout=5s \
|
||||
CMD SCRIPT_NAME=/health.php SCRIPT_FILENAME=/preauth/health.php REQUEST_METHOD=GET \
|
||||
cgi-fcgi -bind -connect localhost:9000 | grep 'online' || exit 1
|
||||
|
||||
ENTRYPOINT /preauth/init.sh
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Preauth\\": "src/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"spomky-labs/otphp": "^11.3",
|
||||
"symfony/http-foundation": "^7.3",
|
||||
"symfony/uid": "^7.3"
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# required, if missing will generate random values
|
||||
# encryption key used to store sessions (static random bytes) in base64
|
||||
PREAUTH_KEY=''
|
||||
# TOTP (RFC 6238) secret/token (static random bytes) in base32
|
||||
PREAUTH_TOKEN=''
|
||||
|
||||
# optional, change time-to-live, subdomain, text or colors
|
||||
# how long a session lasts (in minutes): 43200 is 30 days
|
||||
PREAUTH_TTL=43200
|
||||
PREAUTH_SUBDOMAIN='preauth'
|
||||
PREAUTH_BACKGROUND='#029386'
|
||||
PREAUTH_FOREGROUND='#ffffff'
|
||||
PREAUTH_TITLE='Pre-Authentication System'
|
||||
PREAUTH_ID_NAME='Session ID'
|
||||
PREAUTH_TOKEN_NAME='Authentication Token'
|
||||
PREAUTH_SUBMIT_NAME='Submit'
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
/* ensure php is processing */
|
||||
echo implode('', ['o', 'n', 'l', 'i', 'n', 'e', "\n"]);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$key = getenv('PREAUTH_KEY');
|
||||
|
||||
// generate a new key, if not specified
|
||||
if ( ! $key) {
|
||||
$key = base64_encode(random_bytes(64));
|
||||
error_log("ERROR: PREAUTH_KEY is not set, generating a random session encryption key:\n'$key'\nupdate your config or it will be regenerated when you restart.");
|
||||
}
|
||||
|
||||
echo $key;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use OTPHP\TOTP;
|
||||
|
||||
$token = getenv('PREAUTH_TOKEN');
|
||||
|
||||
// generate a new token, if not specified
|
||||
if ( ! $token) {
|
||||
$token = TOTP::generate()->getSecret();
|
||||
error_log("ERROR: PREAUTH_TOKEN is not set, generating random TOTP token:\n'$token'\nupdate your config or it will be regenerated when you restart.");
|
||||
}
|
||||
|
||||
echo $token;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
# ensure we have required settings
|
||||
if [ -z "$PREAUTH_KEY" ]; then
|
||||
export PREAUTH_KEY=$(cd /preauth && php init-key.php)
|
||||
fi
|
||||
|
||||
if [ -z "$PREAUTH_TOKEN" ]; then
|
||||
export PREAUTH_TOKEN=$(cd /preauth && php init-token.php)
|
||||
fi
|
||||
|
||||
exec php-fpm
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use Preauth\Auth;
|
||||
|
||||
/* kickoff the script */
|
||||
$auth = new Auth($_SERVER['HTTP_HOST'] ?? 'example.com');
|
||||
$auth->run();
|
||||
|
||||
/* no additional output most of the time, sometimes we'll continue to display the login screen */
|
||||
if ($auth->showTemplate()) {
|
||||
include __DIR__ . '/template.php';
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Preauth;
|
||||
|
||||
use OTPHP\TOTP;
|
||||
use Symfony\Component\HttpFoundation\IpUtils;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
class Auth {
|
||||
/* the encryption cipher we are using */
|
||||
private const CIPHER = 'camellia-256-ctr';
|
||||
/* only allow A-z 0-9 _ - */
|
||||
private const URL64 = '/[^A-Za-z0-9_-]+/';
|
||||
/* cookie name */
|
||||
private const NAME = '_auth_uuid';
|
||||
/* directory to store sessions in */
|
||||
private const BASE = '/tmp/sessions/';
|
||||
/* top-level-domains which are known to have multiple parts */
|
||||
private const TLD = [
|
||||
'ai' => ['com','net','off','org'],
|
||||
'am' => ['radio'],
|
||||
'com' => ['br','cn','co','de','eu','gr','it','jpn','mex','ru','sa','uk','us','za'],
|
||||
'de' => ['com'],
|
||||
'fm' => ['radio'],
|
||||
'gg' => ['co','net','org'],
|
||||
'in' => ['co','firm','gen','ind','net','org'],
|
||||
'je' => ['co','net','org'],
|
||||
'mx' => ['com','net','org'],
|
||||
'net' => ['gb','hu','in','jp','se','uk'],
|
||||
'nz' => ['co','net','org'],
|
||||
'org' => ['ae','us'],
|
||||
'ph' => ['com','net','org'],
|
||||
'se' => ['com'],
|
||||
'uk' => ['co','me','org'],
|
||||
];
|
||||
|
||||
/** @var string $preauth subdomain (without domain) the login page will use */
|
||||
private string $preauth;
|
||||
/** @var string $domain domain (without subdomains) we are controlling auth for */
|
||||
private string $domain;
|
||||
/** @var int $expire time-to-live of auth cookie */
|
||||
private int $expire;
|
||||
/** @var array $get like $_GET, but based on data given by reverse proxy */
|
||||
private array $get = [];
|
||||
/** @var string $key secret for encryption to store sessions */
|
||||
private string $key;
|
||||
/** @var string $token secret for 2FA token */
|
||||
private string $token;
|
||||
/** @var bool $stop set to false to print login page */
|
||||
private bool $stop = true;
|
||||
/** @var string $id user provided name for their session */
|
||||
private string $id;
|
||||
|
||||
/**
|
||||
* @param string $host domain which auth is relative to
|
||||
*/
|
||||
public function __construct(string $host) {
|
||||
$this->preauth = getenv('PREAUTH_SUBDOMAIN') ?: 'preauth';
|
||||
$this->domain = $this->baseDomain($host);
|
||||
$this->key = base64_decode(getenv('PREAUTH_KEY') ?: '');
|
||||
$this->token = getenv('PREAUTH_TOKEN') ?: '';
|
||||
$this->expire = time() + 60 * ((int)getenv('PREAUTH_TTL') ?: 43200);
|
||||
parse_str((parse_url(($_SERVER['HTTP_X_FORWARDED_URI'] ?? ''), PHP_URL_QUERY) ?: ''), $this->get);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean returns true if we should display login page, false if we are done
|
||||
*/
|
||||
public function showTemplate(): bool {
|
||||
return ( ! $this->stop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string returns the return-to-url, if one was specified, empty string otherwise
|
||||
*/
|
||||
public function getReturnTo(): string {
|
||||
$rt = $this->get['rt'] ?? '';
|
||||
if ( ! is_string($rt)) {
|
||||
$rt = '';
|
||||
}
|
||||
return $rt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string returns our current domain with all subdomains removed
|
||||
*/
|
||||
public function getBaseDomain(): string {
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point of code, review request and determine course of action
|
||||
*/
|
||||
public function run(): void {
|
||||
if ( ! $this->key || ! $this->token) {
|
||||
$this->die();
|
||||
}
|
||||
|
||||
/* already logged in with valid session, return 200, so caddy permits request */
|
||||
$uuid = $this->getExistingUUID();
|
||||
if ($uuid) {
|
||||
echo "ok $this->id";
|
||||
return;
|
||||
}
|
||||
/* if request is valid login attempt, (set cookie and) return to where they came from */
|
||||
if ($this->login()) {
|
||||
if (isset($this->get['rt'])) {
|
||||
header("Location: {$this->get['rt']}");
|
||||
}
|
||||
echo "ok $this->id";
|
||||
return;
|
||||
}
|
||||
|
||||
/* not already logged in, not trying to login, but on auth page, so present login screen */
|
||||
if ($_SERVER['HTTP_HOST'] === "$this->preauth.$this->domain") {
|
||||
header('http/1.1 401 Unauthorized', true, 401);
|
||||
$this->stop = false;
|
||||
} else {
|
||||
/* not already logged in, not trying to login, and not on auth page, so send to login screen */
|
||||
$rt = rawurlencode(
|
||||
($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'https') . '://' .
|
||||
($_SERVER['HTTP_X_FORWARDED_HOST'] ?? $this->domain) .
|
||||
($_SERVER['HTTP_X_FORWARDED_URI'] ?? '/')
|
||||
);
|
||||
header("Location: https://$this->preauth.$this->domain/?rt=$rt");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This lets us determine the base domain:
|
||||
* "service.example.co.uk" into "example.co.uk" and "service.example.com" into "example.com"
|
||||
* @param string $host domain with zero or more subdomains
|
||||
* @return string returns same domain with all subdomains removed
|
||||
*/
|
||||
private function baseDomain(string $host): string {
|
||||
$parts = array_reverse(explode('.', $host));
|
||||
$keep = min(2, count($parts));
|
||||
/* check if host should retain 3 parts, due to TLD */
|
||||
if (count($parts) > 2 && isset(self::TLD[$parts[0]]) && in_array($parts[1], self::TLD[$parts[0]], true)) {
|
||||
$keep = 3;
|
||||
}
|
||||
$parts = array_reverse(array_slice($parts, 0, $keep));
|
||||
return implode('.', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop, critical server config issue
|
||||
*/
|
||||
private function die(): string {
|
||||
error_log('PREAUTH_KEY and/or PREAUTH_TOKEN are not set, unable to continue.');
|
||||
header('http/1.1 500 Internal Server Error', true, 500);
|
||||
include __DIR__ . '/../500.php';
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null returns the existing UUID if there is one and it is valid, null otherwise
|
||||
*/
|
||||
private function getExistingUUID(): ?string {
|
||||
/* filter user input */
|
||||
$encUUID = preg_replace(self::URL64, '', ($_COOKIE[self::NAME] ?? ''));
|
||||
|
||||
/* session exists as a file, in the format '<iv-b64>$<session-name>', where the filename is the encoded-uuid */
|
||||
if ($encUUID && is_file(self::BASE . $encUUID)) {
|
||||
[$iv, $id, $expire] = explode('$', (file_get_contents(self::BASE . $encUUID) ?: '') . '$$');
|
||||
if ($iv) {
|
||||
/* raw-uuid means not encoded, but still encrypted */
|
||||
$rawUUID = base64_decode(strtr($encUUID, '+/', '-_'));
|
||||
$rawIV = base64_decode($iv);
|
||||
$uuid = openssl_decrypt($rawUUID, self::CIPHER, $this->key, 0, $rawIV);
|
||||
if (Uuid::isValid($uuid) && (int)$expire >= time()) {
|
||||
$this->id = substr(preg_replace(self::URL64, '', $id), 0, 100);
|
||||
return $uuid;
|
||||
}
|
||||
/* we have something encrypted, but it is expired or not valid, so delete the session file */
|
||||
unlink(self::BASE . $encUUID);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string returns a newly generated random uuid
|
||||
*/
|
||||
private function newUUID(): string {
|
||||
$data = random_bytes(16);
|
||||
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
|
||||
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||
}
|
||||
|
||||
/**
|
||||
* Upon valid token and non-empty id, creates a uuid, stores session file and sets the cookie
|
||||
* @return bool returns true if token and id are provided, and token is valid, false otherwise
|
||||
*/
|
||||
private function login(): bool {
|
||||
/* fields must both be filled out */
|
||||
if (isset($this->get['token'], $this->get['id']) && $this->get['token'] && $this->get['id']) {
|
||||
/* filter user input */
|
||||
$id = substr(preg_replace(self::URL64, '', $this->get['id']), 0, 100);
|
||||
$otp = TOTP::createFromSecret($this->token);
|
||||
$date = date('Y-m-d H:i:s');
|
||||
|
||||
/* determine real remote-host, if local address, find next level up */
|
||||
$remoteHost = $_SERVER['REMOTE_HOST'];
|
||||
if (IpUtils::isPrivateIp($remoteHost)) {
|
||||
$remoteList = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'] ?? ''));
|
||||
$remoteIndex = array_search($remoteHost, $remoteList);
|
||||
if ($remoteIndex > 0) {
|
||||
$remoteHost = $remoteList[$remoteIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/* if given token is valid, login, store session file and set the cookie */
|
||||
if ($otp->now() === $this->get['token']) {
|
||||
$this->id = $id;
|
||||
$uuid = $this->newUUID();
|
||||
$rawIV = random_bytes(openssl_cipher_iv_length(self::CIPHER));
|
||||
$rawUUID = openssl_encrypt($uuid, self::CIPHER, $this->key, 0, $rawIV);
|
||||
$encUUID = strtr(base64_encode($rawUUID), '-_', '+/');
|
||||
file_put_contents(self::BASE . $encUUID, base64_encode($rawIV) . "\$$id\$$this->expire\$$date\$$remoteHost\$\n");
|
||||
setcookie(
|
||||
self::NAME,
|
||||
$encUUID,
|
||||
$this->expire,
|
||||
'/', /* all paths */
|
||||
$this->domain, /* all subdomains */
|
||||
true, /* https only */
|
||||
true /* no js access */
|
||||
);
|
||||
error_log("[$date] successful login by id: $id");
|
||||
return true;
|
||||
}
|
||||
/* log failed logins, so we can fail2ban bad actors */
|
||||
error_log("[$date] failed login attempted by ip: $remoteHost");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Preauth;
|
||||
|
||||
class Env {
|
||||
/**
|
||||
* @return string returns title of this system
|
||||
*/
|
||||
public function getTitle(): string {
|
||||
return getenv('PREAUTH_TITLE') ?: 'Pre-Authentication System';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string returns background-color of this system
|
||||
*/
|
||||
public function getColor(): string {
|
||||
return getenv('PREAUTH_BACKGROUND') ?: '#029386'; // teal
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string returns text-color of this system
|
||||
*/
|
||||
public function getTextColor(): string {
|
||||
return getenv('PREAUTH_FOREGROUND') ?: '#ffffff'; // white
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string returns the name of the ID field
|
||||
*/
|
||||
public function getIdName(): string {
|
||||
return getenv('PREAUTH_ID_NAME') ?: 'Session ID';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string returns the name of the Token field
|
||||
*/
|
||||
public function getTokenName(): string {
|
||||
return getenv('PREAUTH_TOKEN_NAME') ?: 'Authentication Token';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string returns the name of the Submit button
|
||||
*/
|
||||
public function getSubmitName(): string {
|
||||
return getenv('PREAUTH_SUBMIT_NAME') ?: 'Submit';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
use Preauth\Env;
|
||||
global $auth;
|
||||
$env = new Env();
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><?php echo $env->getTitle(); ?></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Begin Icons -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="https://<?php echo $auth->getBaseDomain(); ?>/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="https://<?php echo $auth->getBaseDomain(); ?>/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="https://<?php echo $auth->getBaseDomain(); ?>/android-chrome-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="https://<?php echo $auth->getBaseDomain(); ?>/favicon-16x16.png">
|
||||
<link rel="manifest" href="https://<?php echo $auth->getBaseDomain(); ?>/site.webmanifest">
|
||||
<meta name="apple-mobile-web-app-title" content="<?php echo $env->getTitle(); ?>">
|
||||
<meta name="application-name" content="<?php echo $env->getTitle(); ?>">
|
||||
<meta name="msapplication-TileColor" content="<?php echo $env->getColor(); ?>">
|
||||
<meta name="theme-color" content="<?php echo $env->getColor(); ?>">
|
||||
<!-- End Icons -->
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0.25em;
|
||||
}
|
||||
html {
|
||||
background-color: <?php echo $env->getColor(); ?>;
|
||||
color: <?php echo $env->getTextColor(); ?>;
|
||||
display: table;
|
||||
font-family: sans-serif;
|
||||
font-size: 1.5em;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
body {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
}
|
||||
form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
form div {
|
||||
width: 45%;
|
||||
}
|
||||
div.right {
|
||||
text-align: right;
|
||||
}
|
||||
div.center {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1><?php echo $env->getTitle(); ?></h1>
|
||||
<form action="/" method="get">
|
||||
<input type="hidden" name="rt" value="<?php echo $auth->getReturnTo(); ?>">
|
||||
<div class="right"><label for="id"><?php echo $env->getIdName(); ?>:</label></div>
|
||||
<div><input type="text" name="id" id="id" autocomplete="on" required="required" autofocus="autofocus"></div>
|
||||
<div class="right"><label for="token"><?php echo $env->getTokenName(); ?>:</label></div>
|
||||
<div><input type="text" name="token" id="token" autocomplete="off" required="required"></div>
|
||||
<div class="center"><button type="submit"><?php echo $env->getSubmitName(); ?></button></div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user