diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..d18b1ba --- /dev/null +++ b/.drone.yml @@ -0,0 +1,13 @@ +kind: pipeline +name: default + +steps: +- name: docker + image: plugins/docker + settings: + repo: traverseda/glauth-ui + auto_tag: true + username: + from_secret: dockerhub_user + password: + from_secret: dockerhub_pass diff --git a/Dockerfile b/Dockerfile index 0198236..938d3f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8-alpine +FROM python:3.9-alpine RUN adduser -D ldap @@ -24,4 +24,4 @@ USER ldap VOLUME ["/home/ldap/db"] EXPOSE 5000 -ENTRYPOINT ["./boot.sh"] \ No newline at end of file +ENTRYPOINT ["./boot.sh"] diff --git a/README.md b/README.md index eaeb361..ffc091b 100644 --- a/README.md +++ b/README.md @@ -26,50 +26,16 @@ Missing features: The best installation method atm is to build the docker image with the included Dockerfile. -1. Clone Repository ``` git clone https://github.com/sonicnkt/glauth-ui.git glauth-ui +docker-compose -f docker-compose.dev.yaml up -d --build #Buld and deploy with docker compose ``` -2. Run docker build -``` -cd glauth-ui -docker build -t glauthui:latest . - -``` -3. Create container - -`docker-compose.yaml` -``` -version: '3.8' -services: - glauthui: - image: glauthui:latest - container_name: glauthui - restart: unless-stopped - ports: - - 80:5000 - volumes: - # Mount Folder that contains DB and config file outside the container - - './docker-data:/home/ldap/db' - environment: - - SECRET_KEY=mysuperlongsecretkeythatnobodywillguess - # MAIL CONFIG - - MAIL_SERVER=mail.example.com - - MAIL_PORT=587 - - MAIL_USE_TLS=1 - - MAIL_USERNAME=username - - MAIL_PASSWORD=password - - MAIL_ADMIN=admin@example.com -``` -`docker-compose up #-d` On first startup (or if DB is empty) a sample database will be created with 2 users and 4 groups. Use the username "j_doe" and password "dogood" to login and have access to the administration interface. This should be run behind a reverse proxy like nginx that handles https! -4. Point glauth to the config.cfg created by glauth-ui - ---- ### Environment Variables: diff --git a/app/__init__.py b/app/__init__.py index b834aa7..1d589eb 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,4 +1,4 @@ -from flask import Flask +from flask import Flask, request import logging from logging.handlers import SMTPHandler, RotatingFileHandler # https://bootstrap-flask.readthedocs.io @@ -22,7 +22,6 @@ mail = Mail(app) bootstrap = Bootstrap(app) - if not app.debug: if app.config['MAIL_SERVER']: auth = None @@ -72,8 +71,10 @@ def check_password_hash(hash, password): def createdbdata(): """Creating example db""" if models.User.query.count() == 0: - app.logger.info('No Data in DB, creating example dataset') + app.logger.info('No Data in DB, creating example dataset') click.echo('Creating Example DB') models.create_basic_db() else: - app.logger.info('Data in DB allready exists.') \ No newline at end of file + app.logger.info('Data in DB allready exists.') + + diff --git a/app/forms.py b/app/forms.py index 13ceda1..cb8e34b 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,5 +1,5 @@ from flask_wtf import FlaskForm -from wtforms import StringField, PasswordField, BooleanField, SubmitField +from wtforms import StringField, PasswordField, BooleanField, SubmitField, HiddenField from wtforms.validators import DataRequired, ValidationError, Email, EqualTo, Length from app.models import User from app import check_password_hash diff --git a/app/models.py b/app/models.py index dcd7694..923944b 100644 --- a/app/models.py +++ b/app/models.py @@ -65,6 +65,17 @@ class User(UserMixin, db.Model): othergroups = db.relationship('Group', secondary=othergroups_users, backref=db.backref('o_users', lazy='dynamic')) + def in_groups(self,*allowed_groups): + """Check is the user is in a group + """ + primarygroup=Group.query.filter_by(unixid=self.primarygroup).first() + if primarygroup.name in allowed_groups: + return True + for group in self.othergroups: + if group.name in allowed_groups: + return True + return False + @property def is_admin(self): # checks if the name of any group matches the configured ADMIN_GROUP name @@ -117,6 +128,19 @@ def verify_new_account_token(token): def load_user(id): return User.query.get(int(id)) +import base64 + +@login.request_loader +def http_basic_auth(request): + authstr = request.headers.get("Authorization") + if authstr: + authstr=authstr.removeprefix("Basic ").lstrip().rstrip() + authbytes = authstr.encode('utf-8') #We need to ensure it's bytes + username, password = base64.b64decode(authbytes).decode("utf-8").split(":") + user = User.query.filter_by(username=username).first() + if user.check_password(password): + return user + return None def create_basic_db(): settings = Settings(debug=True, ldap_enabled=True, ldap_listen='0.0.0.0:389', basedn='dc=glauth-example,dc=com') @@ -142,4 +166,4 @@ def create_basic_db(): db.session.add(u1) db.session.add(u2) - db.session.commit() \ No newline at end of file + db.session.commit() diff --git a/app/routes.py b/app/routes.py index bdbff9a..1fe9f1d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,6 +1,6 @@ -from flask import render_template, flash, redirect, url_for, request, abort +from flask import render_template, flash, redirect, url_for, request, abort, Response from app import app, db -from app.forms import LoginForm, EditProfileForm, ChangePasswordForm +from app.forms import LoginForm, EditProfileForm, ChangePasswordForm from app.forms import ResetPasswordRequestForm, ResetPasswordForm, NewAccountForm from app.forms import TestMailForm from flask_login import current_user, login_user, logout_user, login_required @@ -34,8 +34,8 @@ def testmail(): def login(): if current_user.is_authenticated: return redirect(url_for('index')) - - form = LoginForm() + + form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() if user is None or not user.check_password(form.password.data): @@ -48,8 +48,9 @@ def login(): flash('Account not eligable to login.') return redirect(url_for('login')) login_user(user, remember=form.remember_me.data) + next_page = request.args.get('next') - if not next_page or url_parse(next_page).netloc != '': + if not next_page or url_parse(next_page).netloc != '': return redirect(url_for('index')) return redirect(next_page) return render_template('login.html', title='Sign In', form=form) @@ -148,4 +149,40 @@ def new_account(token): flash('Your password has been set, please login.') return redirect(url_for('login')) fullname = '{}'.format(user.givenname + ' ' + user.surname) - return render_template('new_account.html', title='Activate Account', form=form, fullname=fullname) \ No newline at end of file + return render_template('new_account.html', title='Activate Account', form=form, fullname=fullname) + +import base64 +from ipaddress import ip_network, ip_address + +@app.route('/forward_auth/header/', methods=['GET', 'POST']) +def forward_auth(): + """The actual login is handled by flask_login + """ + protocol = request.headers.get('X-Forwarded-Proto') + host = request.headers.get('X-Forwarded-Host') + uri = request.headers.get('X-Forwarded-Uri') + origin = protocol+"://"+host+uri + method = request.headers.get('X-Forwarded-Method') + + #whitelist based on IP address + sourceIp=request.headers.get('X-Forwarded-For',None) + if sourceIp in request.args.getlist('ip'): + return "", 201 + + #Whitelist based on CIDR netmask + for net in request.args.getlist('network'): + net = ip_network(net) + if sourceIp and ip_address(sourceIp) in net: + return "", 201 + + if current_user.is_anonymous: + return Response( + f'Could not verify your access level for that {origin}.\n' + 'You have to login with proper credentials\n', 401, + {'WWW-Authenticate': 'Basic realm="Login Required"'}) + + allowed_groups = request.args.getlist('group') + if current_user.in_groups(*allowed_groups): + return "", 201 + + return abort(403) diff --git a/app/static/htmx.min.js b/app/static/htmx.min.js new file mode 100644 index 0000000..8ac2528 --- /dev/null +++ b/app/static/htmx.min.js @@ -0,0 +1 @@ +(function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else{e.htmx=t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var v={onLoad:b,process:rt,on:z,off:V,trigger:lt,ajax:$t,find:S,findAll:E,closest:T,values:function(e,t){var r=Rt(e,t||"post");return r.values},remove:C,addClass:O,removeClass:A,toggleClass:L,takeClass:R,defineExtension:Kt,removeExtension:Qt,logAll:w,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:100,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,attributesToSettle:["class","style","width","height"],wsReconnectDelay:"full-jitter",disableSelector:"[hx-disable], [data-hx-disable]"},parseInterval:f,_:e,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){return new WebSocket(e,[])}};var t=["get","post","put","delete","patch"];var n=t.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function f(e){if(e==undefined){return undefined}if(e.slice(-2)=="ms"){return parseFloat(e.slice(0,-2))||undefined}if(e.slice(-1)=="s"){return parseFloat(e.slice(0,-1))*1e3||undefined}return parseFloat(e)||undefined}function l(e,t){return e.getAttribute&&e.getAttribute(t)}function a(e,t){return e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function I(e,t){return l(e,t)||l(e,"data-"+t)}function c(e){return e.parentElement}function M(){return document}function h(e,t){if(t(e)){return e}else if(c(e)){return h(c(e),t)}else{return null}}function k(e,t){var r=null;h(e,function(e){return r=I(e,t)});return r}function d(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function r(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function i(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=M().createDocumentFragment()}return i}function u(e){var t=r(e);switch(t){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return i(""+e+"
",1);case"col":return i(""+e+"
",2);case"tr":return i(""+e+"
",2);case"td":case"th":return i(""+e+"
",3);case"script":return i("
"+e+"
",1);default:return i(e,0)}}function D(e){if(e){e()}}function o(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function s(e){return o(e,"Function")}function g(e){return o(e,"Object")}function F(e){var t="htmx-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function p(e){var t=[];if(e){for(var r=0;r=0}function P(e){return M().body.contains(e)}function y(e){return e.trim().split(/\s+/)}function U(e,t){for(var r in t){if(t.hasOwnProperty(r)){e[r]=t[r]}}return e}function x(e){try{return JSON.parse(e)}catch(e){ut(e);return null}}function e(e){return Ut(M().body,function(){return eval(e)})}function b(t){var e=v.on("htmx:load",function(e){t(e.detail.elt)});return e}function w(){v.logger=function(e,t,r){if(console){console.log(t,e,r)}}}function S(e,t){if(t){return e.querySelector(t)}else{return S(M(),e)}}function E(e,t){if(t){return e.querySelectorAll(t)}else{return E(M(),e)}}function C(e,t){e=N(e);if(t){setTimeout(function(){C(e)},t)}else{e.parentElement.removeChild(e)}}function O(e,t,r){e=N(e);if(r){setTimeout(function(){O(e,t)},r)}else{e.classList.add(t)}}function A(e,t,r){e=N(e);if(r){setTimeout(function(){A(e,t)},r)}else{e.classList.remove(t)}}function L(e,t){e=N(e);e.classList.toggle(t)}function R(e,t){e=N(e);X(e.parentElement.children,function(e){A(e,t)});O(e,t)}function T(e,t){e=N(e);if(e.closest){return e.closest(t)}else{do{if(e==null||d(e,t)){return e}}while(e=e&&c(e))}}function q(e,t){if(t.indexOf("closest ")===0){return[T(e,t.substr(8))]}else if(t.indexOf("find ")===0){return[S(e,t.substr(5))]}else{return M().querySelectorAll(t)}}function H(e,t){return q(e,t)[0]}function N(e){if(o(e,"String")){return S(e)}else{return e}}function j(e,t,r){if(s(t)){return{target:M().body,event:e,listener:t}}else{return{target:N(e),event:t,listener:r}}}function z(t,r,n){tr(function(){var e=j(t,r,n);e.target.addEventListener(e.event,e.listener)});var e=s(r);return e?r:n}function V(t,r,n){tr(function(){var e=j(t,r,n);e.target.removeEventListener(e.event,e.listener)});return s(r)?r:n}function W(e){var t=h(e,function(e){return I(e,"hx-target")!==null});if(t){var r=I(t,"hx-target");if(r==="this"){return t}else{return H(e,r)}}else{var n=F(e);if(n.boosted){return M().body}else{return e}}}function _(e){var t=v.config.attributesToSettle;for(var r=0;r0){i=e.substr(0,e.indexOf(":"));n=e.substr(e.indexOf(":")+1,e.length)}else{i=e}var o=M().querySelector(n);if(o){var a;a=M().createDocumentFragment();a.appendChild(t);if(!$(i,o)){a=t}le(i,o,o,a,r)}else{t.parentNode.removeChild(t);ot(M().body,"htmx:oobErrorNoTarget",{content:t})}return e}function Z(e,r){X(E(e,"[hx-swap-oob], [data-hx-swap-oob]"),function(e){var t=I(e,"hx-swap-oob");if(t!=null){J(t,e,r)}})}function G(e){X(E(e,"[hx-preserve], [data-hx-preserve]"),function(e){var t=I(e,"id");var r=M().getElementById(t);if(r!=null){e.parentNode.replaceChild(r,e)}})}function Y(n,e,i){X(e.querySelectorAll("[id]"),function(e){if(e.id&&e.id.length>0){var t=n.querySelector(e.tagName+"[id='"+e.id+"']");if(t&&t!==n){var r=e.cloneNode();B(e,t);i.tasks.push(function(){B(e,r)})}}})}function K(e){return function(){rt(e);Ke(e);Q(e);lt(e,"htmx:load")}}function Q(e){var t="[autofocus]";var r=d(e,t)?e:e.querySelector(t);if(r!=null){r.focus()}}function ee(e,t,r,n){Y(e,r,n);while(r.childNodes.length>0){var i=r.firstChild;e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE&&i.nodeType!==Node.COMMENT_NODE){n.tasks.push(K(i))}}}function te(t){var e=F(t);if(e.webSocket){e.webSocket.close()}if(e.sseEventSource){e.sseEventSource.close()}if(e.listenerInfos){X(e.listenerInfos,function(e){if(t!==e.on){e.on.removeEventListener(e.trigger,e.listener)}})}if(t.children){X(t.children,function(e){te(e)})}}function re(e,t,r){if(e.tagName==="BODY"){return se(e,t)}else{var n=e.previousSibling;ee(c(e),e,t,r);if(n==null){var i=c(e).firstChild}else{var i=n.nextSibling}F(e).replacedWith=i;r.elts=[];while(i&&i!==e){if(i.nodeType===Node.ELEMENT_NODE){r.elts.push(i)}i=i.nextElementSibling}te(e);c(e).removeChild(e)}}function ne(e,t,r){return ee(e,e.firstChild,t,r)}function ie(e,t,r){return ee(c(e),e,t,r)}function oe(e,t,r){return ee(e,null,t,r)}function ae(e,t,r){return ee(c(e),e.nextSibling,t,r)}function se(e,t,r){var n=e.firstChild;ee(e,n,t,r);if(n){while(n.nextSibling){te(n.nextSibling);e.removeChild(n.nextSibling)}te(n);e.removeChild(n)}}function ue(e,t){var r=k(e,"hx-select");if(r){var n=M().createDocumentFragment();X(t.querySelectorAll(r),function(e){n.appendChild(e)});t=n}return t}function le(e,t,r,n,i){switch(e){case"none":return;case"outerHTML":re(r,n,i);return;case"afterbegin":ne(r,n,i);return;case"beforebegin":ie(r,n,i);return;case"beforeend":oe(r,n,i);return;case"afterend":ae(r,n,i);return;default:var o=er(t);for(var a=0;a([\s\S]+?)<\/title>/im;function ce(e){var t=fe.exec(e);if(t){return t[1]}}function he(e,t,r,n,i){var o=ce(n);if(o){var a=S("title");if(a){a.innerHTML=o}else{window.document.title=o}}var s=u(n);if(s){Z(s,i);s=ue(r,s);G(s);return le(e,r,t,s,i)}}function de(e,t,r){var n=e.getResponseHeader(t);if(n.indexOf("{")===0){var i=x(n);for(var o in i){if(i.hasOwnProperty(o)){var a=i[o];if(!g(a)){a={value:a}}lt(r,o,a)}}}else{lt(r,n,[])}}var ve=/\s/;var ge=/[\s,]/;var pe=/[_$a-zA-Z]/;var me=/[_$a-zA-Z0-9]/;var ye=['"',"'","/"];var xe=/[^\s]/;function be(e){var t=[];var r=0;while(r0){var a=t[0];if(a==="]"){n--;if(n===0){if(o===null){i=i+"true"}t.shift();i+=")})";try{var s=Ut(e,function(){return Function(i)()},function(){return true});s.source=i;return s}catch(e){ot(M().body,"htmx:syntax:error",{error:e,source:i});return null}}}else if(a==="["){n++}if(we(a,o,r)){i+="(("+r+"."+a+") ? ("+r+"."+a+") : (window."+a+"))"}else{i=i+a}o=t.shift()}}}function Ee(e,t){var r="";while(e.length>0&&!e[0].match(t)){r+=e.shift()}return r}var Ce="input, textarea, select";function Oe(e){var t=I(e,"hx-trigger");var r=[];if(t){var n=be(t);do{Ee(n,xe);var i=n.length;var o=Ee(n,/[,\[\s]/);if(o!==""){if(o==="every"){var a={trigger:"every"};Ee(n,xe);a.pollInterval=f(Ee(n,ve));r.push(a)}else if(o.indexOf("sse:")===0){r.push({trigger:"sse",sseEvent:o.substr(4)})}else{var s={trigger:o};var u=Se(e,n,"event");if(u){s.eventFilter=u}while(n.length>0&&n[0]!==","){Ee(n,xe);var l=n.shift();if(l==="changed"){s.changed=true}else if(l==="once"){s.once=true}else if(l==="consume"){s.consume=true}else if(l==="delay"&&n[0]===":"){n.shift();s.delay=f(Ee(n,ge))}else if(l==="from"&&n[0]===":"){n.shift();s.from=Ee(n,ge)}else if(l==="target"&&n[0]===":"){n.shift();s.target=Ee(n,ge)}else if(l==="throttle"&&n[0]===":"){n.shift();s.throttle=f(Ee(n,ge))}else{ot(e,"htmx:syntax:error",{token:n.shift()})}}r.push(s)}}if(n.length===i){ot(e,"htmx:syntax:error",{token:n.shift()})}Ee(n,xe)}while(n[0]===","&&n.shift())}if(r.length>0){return r}else if(d(e,"form")){return[{trigger:"submit"}]}else if(d(e,Ce)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function Ae(e){F(e).cancelled=true}function Le(e,t,r,n){var i=F(e);i.timeout=setTimeout(function(){if(P(e)&&i.cancelled!==true){Jt(t,r,e);Le(e,t,I(e,"hx-"+t),n)}},n)}function Re(e){return location.hostname===e.hostname&&l(e,"href")&&l(e,"href").indexOf("#")!==0}function Te(t,r,e){if(t.tagName==="A"&&Re(t)||t.tagName==="FORM"){r.boosted=true;var n,i;if(t.tagName==="A"){n="get";i=l(t,"href")}else{var o=l(t,"method");n=o?o.toLowerCase():"get";i=l(t,"action")}e.forEach(function(e){Ie(t,n,i,r,e,true)})}}function qe(e){return e.tagName==="FORM"||d(e,'input[type="submit"], button')&&T(e,"form")!==null||e.tagName==="A"&&e.href&&(e.getAttribute("href")==="#"||e.getAttribute("href").indexOf("#")!==0)}function He(e,t){return F(e).boosted&&e.tagName==="A"&&t.type==="click"&&t.ctrlKey}function Ne(e,t){var r=e.eventFilter;if(r){try{return r(t)!==true}catch(e){ot(M().body,"htmx:eventFilter:error",{error:e,source:r.source});return true}}return false}function Ie(n,i,o,e,a,s){var u=n;if(a.from){u=S(a.from)}var l=function(e){if(!P(n)){u.removeEventListener(a.trigger,l);return}if(He(n,e)){return}if(s||qe(n)){e.preventDefault()}if(Ne(a,e)){return}var t=F(e);if(t.handledFor==null){t.handledFor=[]}var r=F(n);if(t.handledFor.indexOf(n)<0){t.handledFor.push(n);if(a.consume){e.stopPropagation()}if(a.target&&e.target){if(!d(e.target,a.target)){return}}if(a.once){if(r.triggeredOnce){return}else{r.triggeredOnce=true}}if(a.changed){if(r.lastValue===n.value){return}else{r.lastValue=n.value}}if(r.delayed){clearTimeout(r.delayed)}if(r.throttle){return}if(a.throttle){r.throttle=setTimeout(function(){Jt(i,o,n,e);r.throttle=null},a.throttle)}else if(a.delay){r.delayed=setTimeout(function(){Jt(i,o,n,e)},a.delay)}else{Jt(i,o,n,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:a.trigger,listener:l,on:u});u.addEventListener(a.trigger,l)}var Me=false;var ke=null;function De(){if(!ke){ke=function(){Me=true};window.addEventListener("scroll",ke);setInterval(function(){if(Me){Me=false;X(M().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"),function(e){Fe(e)})}},200)}}function Fe(e){var t=F(e);if(!t.revealed&&m(e)){t.revealed=true;Jt(t.verb,t.path,e)}}function Xe(e,t,r){var n=y(r);for(var i=0;i0){lt(l,"htmx:validation:halted",i);return}t.send(JSON.stringify(u));if(qe(l)){e.preventDefault()}})}else{ot(l,"htmx:noWebSocketSourceError")}}function ze(e){var t=v.config.wsReconnectDelay;if(typeof t==="function"){return t(e)}if(t==="full-jitter"){var r=Math.min(e,6);var n=1e3*Math.pow(2,r);return n*Math.random()}ut('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"')}function Ve(e,t,r){var n=y(r);for(var i=0;iv.config.historyCacheSize){i.shift()}while(i.length>0){try{localStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){ot(M().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function dt(e){var t=x(localStorage.getItem("htmx-history-cache"))||[];for(var r=0;r=200&&this.status<400){lt(M().body,"htmx:historyCacheMissLoad",i);var e=u(this.response);e=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;var t=ct();var r=Ft(t);se(t,e,r);mt(r.tasks);ft=n;lt(M().body,"htmx:historyRestore",{path:n})}else{ot(M().body,"htmx:historyCacheMissLoadError",i)}};e.send()}function xt(e){gt();e=e||location.pathname+location.search;var t=dt(e);if(t){var r=u(t.content);var n=ct();var i=Ft(n);se(n,r,i);mt(i.tasks);document.title=t.title;window.scrollTo(0,t.scroll);ft=e;lt(M().body,"htmx:historyRestore",{path:e})}else{if(v.config.refreshOnHistoryMiss){window.location.reload(true)}else{yt(e)}}}function bt(e){var t=k(e,"hx-push-url");return t&&t!=="false"||e.tagName==="A"&&F(e).boosted}function wt(e){var t=k(e,"hx-push-url");return t==="true"||t==="false"?null:t}function St(e){var t=k(e,"hx-indicator");if(t){var r=q(e,t)}else{r=[e]}X(r,function(e){e.classList["add"].call(e.classList,v.config.requestClass)});return r}function Et(e){X(e,function(e){e.classList["remove"].call(e.classList,v.config.requestClass)})}function Ct(e,t){for(var r=0;r=0}function kt(e){var t=k(e,"hx-swap");var r={swapStyle:F(e).boosted?"innerHTML":v.config.defaultSwapStyle,swapDelay:v.config.defaultSwapDelay,settleDelay:v.config.defaultSettleDelay};if(F(e).boosted&&!Mt(e)){r["show"]="top"}if(t){var n=y(t);if(n.length>0){r["swapStyle"]=n[0];for(var i=1;i0){lt(r,"htmx:validation:halted",E);D(o);c();return s}var C=t.split("#");var O=C[0];var A=C[1];if(e==="get"){var L=O;var R=Object.keys(S).length!==0;if(R){if(L.indexOf("?")<0){L+="?"}else{L+="&"}L+=qt(S);if(A){L+="#"+A}}g.open("GET",L,true)}else{g.open(e.toUpperCase(),t,true)}g.overrideMimeType("text/html");for(var T in p){if(p.hasOwnProperty(T)){var q=p[T];Wt(g,T,q)}}var H={xhr:g,target:l,requestConfig:E,pathInfo:{path:t,finalPath:L,anchor:A}};g.onload=function(){try{u(r,H)}catch(e){ot(r,"htmx:onLoadError",U({error:e},H));throw e}finally{Et(N);var e=r;if(!P(r)){e=F(l).replacedWith||l}lt(e,"htmx:afterRequest",H);lt(e,"htmx:afterOnLoad",H);D(o);c()}};g.onerror=function(){Et(N);var e=r;if(!P(r)){e=F(l).replacedWith||l}ot(e,"htmx:afterRequest",H);ot(e,"htmx:sendError",H);D(a);c()};g.onabort=function(){Et(N);var e=r;if(!P(r)){e=F(l).replacedWith||l}ot(e,"htmx:afterRequest",H);ot(e,"htmx:sendAbort",H);D(a);c()};if(!lt(r,"htmx:beforeRequest",H)){D(o);c();return s}var N=St(r);X(["loadstart","loadend","progress","abort"],function(t){X([g,g.upload],function(e){e.addEventListener(t,function(e){lt(r,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});lt(r,"htmx:beforeSend",H);g.send(e==="get"?null:Dt(g,r,S));return s}function Zt(a,s){var u=s.xhr;var l=s.target;if(!lt(a,"htmx:beforeOnLoad",s))return;if(Bt(u,/HX-Trigger:/i)){de(u,"HX-Trigger",a)}if(Bt(u,/HX-Push:/i)){var f=u.getResponseHeader("HX-Push")}if(Bt(u,/HX-Redirect:/i)){window.location.href=u.getResponseHeader("HX-Redirect");return}if(Bt(u,/HX-Refresh:/i)){if("true"===u.getResponseHeader("HX-Refresh")){location.reload();return}}var c=bt(a)||f;if(u.status>=200&&u.status<400){if(u.status===286){Ae(a)}if(u.status!==204){if(!lt(l,"htmx:beforeSwap",s))return;var h=u.response;st(a,function(e){h=e.transformResponse(h,u,a)});if(c){gt()}var d=kt(a);l.classList.add(v.config.swappingClass);var e=function(){try{var e=document.activeElement;var t={elt:e,start:e?e.selectionStart:null,end:e?e.selectionEnd:null};var r=Ft(l);he(d.swapStyle,l,a,h,r);if(t.elt&&!P(t.elt)&&t.elt.id){var n=document.getElementById(t.elt.id);if(n){if(t.start&&n.setSelectionRange){n.setSelectionRange(t.start,t.end)}n.focus()}}l.classList.remove(v.config.swappingClass);X(r.elts,function(e){if(e.classList){e.classList.add(v.config.settlingClass)}lt(e,"htmx:afterSwap",s)});if(s.pathInfo.anchor){location.hash=s.pathInfo.anchor}if(Bt(u,/HX-Trigger-After-Swap:/i)){var i=a;if(!P(a)){i=M().body}de(u,"HX-Trigger-After-Swap",i)}var o=function(){X(r.tasks,function(e){e.call()});X(r.elts,function(e){if(e.classList){e.classList.remove(v.config.settlingClass)}lt(e,"htmx:afterSettle",s)});if(c){var e=f||wt(a)||_t(u)||s.pathInfo.finalPath||s.pathInfo.path;pt(e);lt(M().body,"htmx:pushedIntoHistory",{path:e})}Xt(r.elts,d);if(Bt(u,/HX-Trigger-After-Settle:/i)){var t=a;if(!P(a)){t=M().body}de(u,"HX-Trigger-After-Settle",t)}};if(d.settleDelay>0){setTimeout(o,d.settleDelay)}else{o()}}catch(e){ot(a,"htmx:swapError",s);throw e}};if(d.swapDelay>0){setTimeout(e,d.swapDelay)}else{e()}}}else{ot(a,"htmx:responseError",U({error:"Response Status Error Code "+u.status+" from "+s.pathInfo.path},s))}}var Gt={};function Yt(){return{onEvent:function(e,t){return true},transformResponse:function(e,t,r){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,r,n){return false},encodeParameters:function(e,t,r){return null}}}function Kt(e,t){Gt[e]=U(Yt(),t)}function Qt(e){delete Gt[e]}function er(e,r,n){if(e==undefined){return r}if(r==undefined){r=[]}if(n==undefined){n=[]}var t=I(e,"hx-ext");if(t){X(t.split(","),function(e){e=e.replace(/ /g,"");if(e.slice(0,7)=="ignore:"){n.push(e.slice(7));return}if(n.indexOf(e)<0){var t=Gt[e];if(t&&r.indexOf(t)<0){r.push(t)}}})}return er(c(e),r,n)}function tr(e){if(M().readyState!=="loading"){e()}else{M().addEventListener("DOMContentLoaded",e)}}function rr(){if(v.config.includeIndicatorStyles!==false){M().head.insertAdjacentHTML("beforeend","")}}function nr(){var e=M().querySelector('meta[name="htmx-config"]');if(e){return x(e.content)}else{return null}}function ir(){var e=nr();if(e){v.config=U(v.config,e)}}tr(function(){ir();rr();var e=M().body;rt(e);window.onpopstate=function(e){if(e.state&&e.state.htmx){xt()}};setTimeout(function(){lt(e,"htmx:load",{})},0)});return v}()}); \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index 856b422..32ff64f 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -5,7 +5,6 @@ - {% block styles %} {{ bootstrap.load_css() }} @@ -30,7 +29,7 @@ {% block scripts %} + {{ bootstrap.load_js() }} {% endblock %} diff --git a/app/templates/login.html b/app/templates/login.html index 548440a..9d96969 100644 --- a/app/templates/login.html +++ b/app/templates/login.html @@ -10,4 +10,4 @@

Please sign in

Forgot Your Password? Click to Reset It

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index d532deb..cbd3e65 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -21,9 +21,9 @@ services: glauth: image: glauth/glauth container_name: glauth-dev - command: glauth -c /config/ + command: /app/glauth -c /config/config.cfg volumes: - - 'glauth:/config/config.cfg' + - 'glauth:/config/' volumes: glauth: diff --git a/examples/docker-compose.forwardauth.yaml b/examples/docker-compose.forwardauth.yaml new file mode 100644 index 0000000..d5cfd8b --- /dev/null +++ b/examples/docker-compose.forwardauth.yaml @@ -0,0 +1,53 @@ +version: '3.7' + +services: + traefik: + image: traefik + command: > + --log.level=DEBUG + --api.insecure=true + --providers.docker=true + --providers.docker.exposedbydefault=false + --entrypoints.web.address=:80 + ports: + - "80:80" + - "8080:8080" + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + + whoami: + image: "traefik/whoami" + labels: + traefik.enable: true + traefik.http.routers.whoami.rule: Host(`whoami.localhost`) + traefik.http.services.whoami.loadbalancer.server.port: 80 + traefik.http.routers.whoami.entrypoints: web + traefik.http.routers.whoami.middlewares: whoami + traefik.http.middlewares.whoami.forwardauth.address: http://glauth-ui:5000/forward_auth/header/?group=glauth_admin&group=svcaccts + + glauth-ui: + build: ../ + restart: unless-stopped + volumes: + # Mount Folder that contains DB and config file outside the container + - 'glauth:/home/ldap/db' + environment: + SECRET_KEY: mysuperlongsecretkeythatnobodywillguess + + labels: + traefik.enable: true + traefik.http.routers.auth.rule: Host(`auth.localhost`) + traefik.http.services.auth.loadbalancer.server.port: 5000 + traefik.http.routers.auth.entrypoints: web + + glauth: + image: glauth/glauth + ports: + - "389:389" + command: /app/glauth -c /config/config.cfg + volumes: + - 'glauth:/config/' + +volumes: + glauth: +