Browse Source

Initial commit

mistress
Daniel Muckerman 3 years ago
commit
4172639589
14 changed files with 720 additions and 0 deletions
  1. +8
    -0
      .gitea/template
  2. +18
    -0
      .gitignore
  3. +13
    -0
      Dockerfile
  4. +100
    -0
      app.py
  5. +22
    -0
      manifest.json
  6. +7
    -0
      requirements.txt
  7. BIN
      static/app-icon.png
  8. +1
    -0
      static/feather-sprite.svg
  9. +313
    -0
      static/style.css
  10. +64
    -0
      templates/about.j2
  11. +37
    -0
      templates/fragments/navbar.j2
  12. +39
    -0
      templates/fragments/sidebar.j2
  13. +50
    -0
      templates/home.j2
  14. +48
    -0
      templates/login.j2

+ 8
- 0
.gitea/template View File

@ -0,0 +1,8 @@
# All jinja files, anywhere in the repository
**.j2
# Manifest.json
manifest.json
# Dockerfile
Dockerfile

+ 18
- 0
.gitignore View File

@ -0,0 +1,18 @@
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
.envrc
.DS_Store
.vscode/

+ 13
- 0
Dockerfile View File

@ -0,0 +1,13 @@
FROM python:3.7
LABEL maintainer="$REPO_OWNER"
WORKDIR /project
ADD . /project
RUN rm /project/.envrc
RUN rm -rf /project/env
RUN apt-get update && apt-get install -y python3-dev libldap2-dev libsasl2-dev libssl-dev
RUN pip install -r requirements.txt
CMD ["flask","run","--host=0.0.0.0"]

+ 100
- 0
app.py View File

@ -0,0 +1,100 @@
import ldap as l
from ldap3 import Server, Connection, ALL, MODIFY_REPLACE
from flask import Flask, g, request, session, redirect, url_for, render_template, send_from_directory
from flask_simpleldap import LDAP
from flask_bootstrap import Bootstrap
import os
from flask_cache_buster import CacheBuster
app = Flask(__name__)
Bootstrap(app)
app.secret_key = 'asdf'
app.debug = True
# Base
app.config['LDAP_REALM_NAME'] = 'OpenLDAP Authentication'
app.config['LDAP_HOST'] = os.environ.get('LDAP_HOST')
app.config['LDAP_BASE_DN'] = os.environ.get('LDAP_BASE_DN')
app.config['LDAP_USERNAME'] = os.environ.get('LDAP_USERNAME')
app.config['LDAP_PASSWORD'] = os.environ.get('LDAP_PASSWORD')
# OpenLDAP
app.config['LDAP_OBJECTS_DN'] = 'dn'
app.config['LDAP_OPENLDAP'] = True
app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=posixAccount)(uid=%s))'
ldap = LDAP(app)
config = {
'extensions': ['.js', '.css', '.csv'],
'hash_size': 10
}
cache_buster = CacheBuster(config=config)
cache_buster.register_cache_buster(app)
server = Server(app.config['LDAP_HOST'])
conn = Connection(server, app.config['LDAP_USERNAME'], app.config['LDAP_PASSWORD'], auto_bind=True)
@app.before_request
def before_request():
g.user = None
if 'user_id' in session:
# This is where you'd query your database to get the user info.
g.user = {}
@app.route("/manifest.json")
def manifest():
return send_from_directory('./', 'manifest.json')
@app.route('/')
@ldap.login_required
def index():
user_dict = ldap.get_object_details(session['user_id'])
if 'user_id' in session:
user = {'dn': 'cn={},cn=usergroup,ou=users,dc=technicalincompetence,dc=club'.format(user_dict['cn'][0].decode('ascii')),
'firstName': user_dict['givenName'][0].decode('ascii'),
'lastName': user_dict['sn'][0].decode('ascii'),
'email': user_dict['mail'][0].decode('ascii'),
'userName': user_dict['uid'][0].decode('ascii'),
}
return render_template('home.j2')
@app.route('/about')
@ldap.login_required
def about():
return render_template('about.j2')
@app.route('/login', methods=['GET', 'POST'])
def login():
if g.user:
return redirect(url_for('index'))
if request.method == 'POST':
user = request.form['user']
passwd = request.form['passwd']
test = ldap.bind_user(user, passwd)
if test is None or passwd == '':
return render_template('login.j2', error='Invalid credentials')
else:
session['user_id'] = request.form['user']
session['passwd'] = request.form['passwd']
return redirect('/')
return render_template('login.j2')
@app.route('/logout')
def logout():
session.pop('user_id', None)
return redirect(url_for('index'))
if __name__ == '__main__':
app.run()

+ 22
- 0
manifest.json View File

@ -0,0 +1,22 @@
{
"short_name": "$REPO_NAME",
"name": "$REPO_NAME",
"description": "$REPO_DESCRIPTION",
"icons": [
{
"src": "/images/icons-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/images/icons-512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/",
"background_color": "#343a40",
"theme_color": "#343a40",
"display": "standalone",
"scope": "/"
}

+ 7
- 0
requirements.txt View File

@ -0,0 +1,7 @@
Flask==1.1.2
Flask-Bootstrap4==4.0.2
Flask-Login==0.5.0
Flask-SimpleLDAP==1.4.0
python-ldap==3.2.0
ldap3==2.7
Flask-Cache-Buster==1.0.1

BIN
static/app-icon.png View File

Before After
Width: 1024  |  Height: 1024  |  Size: 43 KiB

+ 1
- 0
static/feather-sprite.svg
File diff suppressed because it is too large
View File


+ 313
- 0
static/style.css View File

@ -0,0 +1,313 @@
/* Scroll fix, try to disable overflow scrolling on mobile to make this feel more native */
html, body {
width: 100%;
height: 100%;
overflow: hidden;
position: fixed;
padding-bottom: 0;
overscroll-behavior-y: none;
}
/* Hide bootstrap tooltip arrows
I did this so I wouldn't have to deal with toolbar button alignment but you can change that if you want */
.tooltip .arrow {
display: none;
}
/* Navbar */
.navbar {
position: absolute;
top: 0;
width: 100%;
}
.toolbar-button {
padding-left: 2rem;
padding-top: .375rem;
line-height: 1.5;
color: white;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.toolbar-button:hover {
color: #ddd;
text-decoration: none;
}
/* Sidebar */
#sidebar {
padding-left: 20px;
background-color: #ecedee;
height: 100vh;
padding-top: 20px;
border-right: 1px solid darkgray;
position: fixed;
width: 250px;
}
#sidebar a:hover {
text-decoration: none;
}
#sidebar ul > li {
padding-bottom: 10px;
}
#sidebar ul > li > a {
color: rgba(0,0,0,.5);
}
#sidebar ul > li.active > a {
color: black;
}
#sidebar div > a {
color: rgba(0,0,0,.5);
}
#sidebar div.active > a {
color: black;
}
/* Content area */
.wrapper {
height: calc(100% - 56px);
overflow: auto;
position: fixed;
top: 56px;
width: 100%;
-webkit-overflow-scrolling: touch;
align-items: stretch;
}
#content {
width: 100%;
padding: 20px;
}
/* Dark theming */
@media (prefers-color-scheme: dark) {
/* Custom Bootstrap dark theming */
body {
background-color: #111 !important;
color: #eee;
}
.jumbotron {
background-color: #333 !important;
}
.modal-content {
background-color: #111 !important;
color: #eee;
}
.modal-header {
border-bottom: 1px solid #555 !important;
}
.modal-header .close {
color: #eee !important;
text-shadow: 0 1px 0 #555 !important;
}
.modal-footer {
border-top: 1px solid #555 !important;
}
.bg-light {
background-color: #333 !important;
}
.bg-white {
background-color: #000 !important;
}
.bg-black {
background-color: #eee !important;
}
.form-control {
display: block;
width: 100%;
height: calc(1.5em + 0.75rem + 2px);
padding: 0.375rem 0.75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #dee2e6;
background-color: #000;
background-clip: padding-box;
border: 1px solid #6c757d;
border-radius: 0.25rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
@media (prefers-reduced-motion: reduce) {
.form-control {
transition: none;
}
}
.form-control::-ms-expand {
background-color: transparent;
border: 0;
}
.form-control:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 #dee2e6;
}
.form-control:focus {
color: #dee2e6;
background-color: #191d21;
border-color: #b3d7ff;
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.form-control::-webkit-input-placeholder {
color: #6c757d;
opacity: 1;
}
.form-control::-moz-placeholder {
color: #6c757d;
opacity: 1;
}
.form-control::-ms-input-placeholder {
color: #6c757d;
opacity: 1;
}
.form-control::placeholder {
color: #6c757d;
opacity: 1;
}
.form-control:disabled,
.form-control[readonly] {
background-color: #343a40;
opacity: 1;
}
.card {
background-color: #000;
border: 1px solid #6c757d;
}
/* Sidebar */
#sidebar {
padding-left: 20px;
background-color: #1a1d20;
height: 100vh;
padding-top: 20px;
border-right: 1px solid black;
}
#sidebar ul > li {
padding-bottom: 10px;
}
#sidebar ul > li > a {
color: rgba(255,255,255,.5);
}
#sidebar ul > li.active > a {
color: white;
}
#sidebar div > a {
color: rgba(255,255,255,.5);
}
#sidebar div.active > a {
color: white;
}
}
/* PWA theming fixes
Tested basically only on iOS, so Android, get fucked maybe */
@media all and (display-mode: standalone) {
.navbar {
position: absolute;
top: 0;
width: 100%;
padding-top: 44px !important;
}
.wrapper {
height: calc(100vh - 92px);
overflow: auto;
position: fixed;
top: 92px;
width: 100%;
align-items: stretch;
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
}
/* Tweaks for size classes above portait phones */
@media (min-width: 577px) {
.navbar {
position: absolute;
top: 0;
width: 100%;
padding-top: 24px !important;
}
#navbarNavDropdown {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
.wrapper {
height: calc(100% - 72px);
overflow: auto;
position: fixed;
top: 72px;
width: 100%;
align-items: stretch;
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-insert-bottom);
}
}
}
/* Hide sidebar on phones and portrait tablets */
@media (max-width: 991.98px) {
#sidebar {
display: none !important;
}
}
/* Display sidebar and hide nav menu for landscape tablets and desktop */
@media (min-width: 991.98px) {
.navbar-nav {
display: none !important;
}
#sidebar {
min-width: 250px;
max-width: 250px;
display: block !important;
}
#content {
left: 250px;
width: calc(100% - 250px) !important;
position: relative;
}
#sidebar.active {
margin-left: -250px;
}
}

+ 64
- 0
templates/about.j2 View File

@ -0,0 +1,64 @@
{% extends "bootstrap/base.html" %}
{% block title %}$REPO_NAME{% endblock %}
{% block metas %}
{{super()}}
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<link rel="manifest" crossorigin="use-credentials" href="./manifest.json">
<link rel="apple-touch-icon" href="{{url_for('.static', filename='app-icon.png')}}">
{% endblock %}
{% block styles %}
{{super()}}
<link rel="stylesheet" href="{{url_for('.static', filename='style.css')}}">
{% endblock %}
{% block navbar %}
{% include "fragments/navbar.j2" %}
{% endblock %}
{% block content %}
<div class="wrapper">
<!-- Sidebar -->
<div id="sidebar">
{% include "fragments/sidebar.j2" %}
</div>
<!-- Page Content -->
<div id="content">
<div>
Made with <svg
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="height: 1rem; vertical-align: middle; margin-bottom: 4px;">
<use xlink:href="{{url_for('.static', filename='feather-sprite.svg')}}#heart"/></svg> and <svg
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="height: 1rem; vertical-align: middle; margin-bottom: 4px;">
<use xlink:href="{{url_for('.static', filename='feather-sprite.svg')}}#coffee"/></svg> in Outer Austin!
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
// Enable bootstrap tooltips
$(function () {
$('[data-toggle="tooltip"]').tooltip()
});
</script>
{% endblock %}

+ 37
- 0
templates/fragments/navbar.j2 View File

@ -0,0 +1,37 @@
<div id="navbar" class="navbar navbar-expand-lg sticky-top navbar-dark bg-dark">
<div class="navbar-brand">$REPO_NAME</div>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown"
aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav">
{% if request.path == url_for('index') %}
<li class="nav-item active">
{% else %}
<li class="nav-item">
{% endif %}
<a class="nav-link" href="{{ url_for('index') }}">Home</a>
</li>
{% if request.path == url_for('about') %}
<li class="nav-item active">
{% else %}
<li class="nav-item">
{% endif %}
<a class="nav-link" href="{{ url_for('about') }}">About</a>
</li>
</ul>
<form class="form-inline ml-auto">
<a class="toolbar-button" href="/logout" style="padding-right: 1rem;" data-toggle="tooltip" data-placement="bottom" title="Logout"><svg
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="line-height: 1.5; vertical-align: middle; margin-bottom: 4px;">
<use xlink:href="{{url_for('.static', filename='feather-sprite.svg')}}#log-out"/></svg></a>
</form>
</div>
</div>

+ 39
- 0
templates/fragments/sidebar.j2 View File

@ -0,0 +1,39 @@
<ul class="list-unstyled components">
{% if request.path == url_for('index') %}
<li class="nav-item active">
{% else %}
<li class="nav-item">
{% endif %}
<a href="{{ url_for('index') }}">
<svg
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="height: 1rem; vertical-align: middle; margin-bottom: 4px;">
<use xlink:href="{{url_for('.static', filename='feather-sprite.svg')}}#home"/>
</svg>Home
</a>
</li>
</ul>
{% if request.path == url_for('about') %}
<div style="position: fixed; bottom: 20px;" class="active">
{% else %}
<div style="position: fixed; bottom: 20px;">
{% endif %}
<a href="{{ url_for('about') }}"><svg
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="height: 1rem; vertical-align: middle; margin-bottom: 4px;">
<use xlink:href="{{url_for('.static', filename='feather-sprite.svg')}}#coffee"/>
</svg>About</a>
</div>

+ 50
- 0
templates/home.j2 View File

@ -0,0 +1,50 @@
{% extends "bootstrap/base.html" %}
{% block title %}$REPO_NAME{% endblock %}
{% block metas %}
{{super()}}
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<link rel="manifest" crossorigin="use-credentials" href="./manifest.json">
<link rel="apple-touch-icon" href="{{url_for('.static', filename='app-icon.png')}}">
{% endblock %}
{% block styles %}
{{super()}}
<link rel="stylesheet" href="{{url_for('.static', filename='style.css')}}">
{% endblock %}
{% block navbar %}
{% include "fragments/navbar.j2" %}
{% endblock %}
{% block content %}
<div class="wrapper">
<!-- Sidebar -->
<div id="sidebar">
{% include "fragments/sidebar.j2" %}
</div>
<!-- Page Content -->
<div id="content">
<form>
<div class="row justify-content-center">
<div class="col-lg-12">
This is your home page, have fun!
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script>
// Enable bootstrap tooltips
$(function () {
$('[data-toggle="tooltip"]').tooltip()
});
</script>
{% endblock %}

+ 48
- 0
templates/login.j2 View File

@ -0,0 +1,48 @@
{% extends "bootstrap/base.html" %}
{% block title %}$REPO_NAME{% endblock %}
{% block metas %}
{{super()}}
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<link rel="manifest" crossorigin="use-credentials" href="./manifest.json">
<link rel="apple-touch-icon" href="{{url_for('.static', filename='app-icon.png')}}">
{% endblock %}
{% block styles %}
{{super()}}
<link rel="stylesheet" href="{{url_for('.static', filename='style.css')}}">
{% endblock %}
{% block navbar %}
<nav class="navbar navbar-expand-lg sticky-top navbar-dark bg-dark">
<div class="navbar-brand">$REPO_NAME</div>
</nav>
{% endblock %}
{% block content %}
<div class="wrapper">
<div class="container" style="margin-top: 15px">
<div class="jumbotron">
<h1>Sign in for our awesome service</h1>
<p>Forgot your password? Too bad! We don't have emails working yet!</p>
</div>
<div class="col-md-12">
{% if error is defined %}
<div id="error-alert" class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endif %}
<form action="" method="post">
<label for="exampleFormControlInput1">Username</label>
<input name="user" class="form-control"><br>
<label for="exampleFormControlInput2">Password</label>
<input type="password" name="passwd" class="form-control"><br>
<button type="submit" class="btn btn-primary">Log In</button>
</form>
</div>
</div>
{% endblock %}

Loading…
Cancel
Save