This commit is contained in:
Freya Murphy 2024-05-27 00:29:36 -04:00
commit cb9d1193c3
Signed by: freya
GPG key ID: 744AB800E383AE52
15 changed files with 394 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
data

21
build/nginx/Dockerfile Normal file
View file

@ -0,0 +1,21 @@
FROM alpine:3.19
# install packages
RUN apk add --no-cache nginx shadow tini
RUN rm -fr /var/cache/apk/*
# update nginx user
RUN groupmod --gid 1000 nginx
RUN usermod --uid 1000 nginx
# remove build packages
RUN apk del shadow
# make log syms
RUN ln -sf /dev/stdout /var/log/nginx/access.log && \
ln -sf /dev/stderr /var/log/nginx/error.log
# do the
USER nginx
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/usr/sbin/nginx", "-c", "/etc/nginx/nginx.conf"]

16
build/php/Dockerfile Normal file
View file

@ -0,0 +1,16 @@
FROM php:fpm-alpine
# install packages
RUN apk add --no-cache runuser shadow openldap-dev
RUN rm -fr /var/cache/apk/*
# update php user
RUN groupmod --gid 1000 www-data
RUN usermod --uid 1000 www-data
# install php packages
RUN docker-php-ext-install ldap
# remove build packages
RUN apk del shadow
USER www-data

9
conf/ldap/ldap.env Normal file
View file

@ -0,0 +1,9 @@
LDAP_URL=
LDAP_BIND_DN=
LDAP_BIND_PASSWORD=
LDAP_BASE_DN=
LDAP_FILTER="(&)"
LDAP_UID="cn"
HTTP_HOST=auth.example.com

50
conf/nginx/nginx.conf Normal file
View file

@ -0,0 +1,50 @@
worker_processes 4;
daemon off;
pid /tmp/nginx.pid;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 70;
server_tokens off;
client_max_body_size 2m;
access_log /var/log/nginx/access.log;
server {
listen 8080;
root /opt/website;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml image/x-icon;
location /favicon.ico {
add_header Cache-Control "public, max-age=31536000, immutable";
root /opt/website/public/icons;
}
location /public {
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
location / {
add_header Content-Security-Policy "script-src 'none'; object-src 'none'; base-uri 'none'";
root /opt/website/web;
include fastcgi_params;
fastcgi_pass php:9000;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
}
}
}

21
docker-compose.yml Normal file
View file

@ -0,0 +1,21 @@
services:
web:
build: ./build/nginx
restart: unless-stopped
ports:
- '80:8080'
volumes:
- ./src:/opt/website:ro
- ./conf/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- php
php:
build: ./build/php
restart: unless-stopped
env_file:
- ./conf/ldap/ldap.env
volumes:
- ./src:/opt/website:ro
- ./data/session:/var/lib/php/session

BIN
src/public/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

70
src/public/main.css Normal file
View file

@ -0,0 +1,70 @@
:root {
--blue: rgb(0, 102, 204);
}
* {
box-sizing: border-box;
}
html, body {
height: 100%;
padding: 0;
margin: 0;
}
body {
display: flex;
justify-content: center;
background: #898989;
background-image: url('./bg.jpg');
background-size: 100%;
background-position: 50% 50%;
color: #fff;
font-family: "Open Sans", Helvetica, Arial, sans-serif;
font-weight: 100;
font-size: 12px;
}
main {
margin-top: 20vh;
height: fit-content;
width: 400px;
background: #fff;
color: #000;
}
main .heading {
border-top: 5px solid var(--blue);
font-size: 1.5rem;
text-align: center;
padding: 10px;
}
main .content {
padding: 10px;
}
main .content, form {
display: flex;
flex-direction: column;
}
main label,
main #submit {
margin-top: 10px;
}
main input {
border: 1px solid #ddd;
outline: none;
padding: 7px;
font-size: 14px;
border-bottom-color: black;
}
main #submit {
background: var(--blue);
color: white;
cursor: pointer;
}

59
src/web/helpers/auth.php Normal file
View file

@ -0,0 +1,59 @@
<?php /* Copyright (c) 2024 Freya Murphy */
$keys = array();
function load_key($key) {
$file = "/tmp/$key";
if (!file_exists($file))
return FALSE;
$content = explode("\n", file_get_contents($file));
return array(
'user' => $content[0],
'time' => $content[1]
);
}
function store_key($key, $user) {
$file = "/tmp/$key";
$now = (string)time();
$content = "$user\n{$now}";
file_put_contents($file, $content, LOCK_EX);
}
function get_random($n)
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';
for ($i = 0; $i < $n; $i++) {
$index = rand(0, strlen($characters) - 1);
$randomString .= $characters[$index];
}
return $randomString;
}
function key_auth() {
if (!isset($_SESSION['auth'])) {
return FALSE;
}
$key = $_SESSION['auth'];
$data = load_key($key);
if ($data === FALSE) {
return FALSE;
}
$user = $data['user'];
$time = $data['time'];
$now = time();
if ($time > $now || $now - $time > 60 * 60 * 24) {
return FALSE;
}
store_key($key, $user);
return $user;
}
function key_new($user) {
$key = get_random(128);
store_key($key, $user);
$_SESSION['auth'] = $key;
}

41
src/web/helpers/ldap.php Normal file
View file

@ -0,0 +1,41 @@
<?php /* Copyright (c) 2024 Freya Murphy */
function ldap_auth($auth_username, $auth_password) {
$url = getenv("LDAP_URL");
$bind = getenv("LDAP_BIND_DN");
$password = getenv("LDAP_BIND_PASSWORD");
$bound = getenv("LDAP_BASE_DN");
$filter = getenv("LDAP_FILTER");
$uid = getenv("LDAP_UID");
$conn = @ldap_connect($url);
if (!$conn) {
return NULL;
}
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
$bind_conn = @ldap_bind($conn, $bind, $password);
if (!$bind_conn) {
return NULL;
}
$search = @ldap_search($conn, $bound, $filter);
$info = @ldap_get_entries($conn, $search);
$user = NULL;
for ($i=0; $i<$info['count']; $i++) {
$user = $info[$i];
if (!array_key_exists($uid, $user))
continue;
if ($user[$uid][0] == $auth_username)
break;
}
if ($user == NULL) {
return FALSE;
}
$succ = @ldap_bind($conn, $user['dn'], $auth_password);
return !!$succ;
}

66
src/web/index.php Normal file
View file

@ -0,0 +1,66 @@
<?php /* Copyright (c) 2024 Freya Murphy */
ini_set('html_errors', '1');
$webroot = dirname(__FILE__);
$publicroot = realpath(dirname(__FILE__) . '/../public');
// load stuff
require($webroot . '/helpers/ldap.php');
require($webroot . '/helpers/auth.php');
// start session
session_set_cookie_params(
60 * 60 * 24, // lifetime (seconds),
'/', // path
NULL, // domain,
TRUE, // secure,
TRUE // http only
);
session_start();
function page($file, $data = array()) {
extract($data);
$webroot = $GLOBALS['webroot'];
require($webroot . '/views/header.php');
require($webroot . "/views/$file.php");
require($webroot . '/views/footer.php');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
parse_str(file_get_contents('php://input'), $post);
$res = ldap_auth($post['username'], $post['password']);
$msg = '';
$title = '';
if ($res) {
$msg = 'Authenticated. You can now go back to your content';
$title = 'Success';
key_new($post['username']);
} else {
$msg = 'Invalid Credentials';
$title = 'Error';
}
page('message', array(
'title' => $title,
'msg' => $msg
));
} else {
if (($user = key_auth())) {
http_response_code(200);
header("X-Webauth-User: $user");
die();
}
$host = $_SERVER['HTTP_HOST'];
$env = getenv("HTTP_HOST");
if ($host != $env) {
// we are being forwarded authed
// redirect
http_response_code(301);
header("Location: https://$env");
} else {
page('login', array(
'title' => 'Login'
));
}
}

4
src/web/views/footer.php Normal file
View file

@ -0,0 +1,4 @@
<?php /* Copyright (c) 2024 Freya Murphy */ ?>
</main>
</body>
</html>

13
src/web/views/header.php Normal file
View file

@ -0,0 +1,13 @@
<?php /* Copyright (c) 2024 Freya Murphy */ ?>
<!DOCTYPE html>
<html>
<head>
<link href="//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&amp;subset=latin" rel="stylesheet">
<link rel="stylesheet" href="/public/main.css">
</head>
<body>
<main id="main" role="main">
<div class="heading">
<span><?=$title?></span>
</div>
<div class="content">

22
src/web/views/login.php Normal file
View file

@ -0,0 +1,22 @@
<?php /* Copyright (c) 2024 Freya Murphy */ ?>
<form method="post">
<label for="username">Username</label>
<input
type="text"
id="username"
name="username"
autofocus="true"
>
<label fot="password">Password</label>
<input
type="password"
id="password"
name="password"
>
<input
type="submit"
role="button"
id="submit"
value="Sign In"
>
<form>

View file

@ -0,0 +1 @@
<center><?=$msg?></center>