feat: Push initial version
This commit is contained in:
parent
924d9639a5
commit
6f195c86a1
28
.gitea/workflows/build.yml
Normal file
28
.gitea/workflows/build.yml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: Build Docker
|
||||||
|
run-name: Build Docker Image
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Build Docker:
|
||||||
|
runs-on: [ubuntu-latest, amd]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Install Docker
|
||||||
|
run : |
|
||||||
|
apt-get install ca-certificates curl gnupg
|
||||||
|
install -m 0755 -d /etc/apt/keyrings
|
||||||
|
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||||
|
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||||
|
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
apt-get update
|
||||||
|
apt-get install docker-ce-cli -y
|
||||||
|
- name: Build Docker image
|
||||||
|
run : |
|
||||||
|
echo "${{ secrets.DOCKERGIT_TOKEN }}" | docker login git.woodburn.au -u nathanwoodburn --password-stdin
|
||||||
|
tag_num=$(git rev-parse --short HEAD)
|
||||||
|
docker build -t hns-login:$tag_num .
|
||||||
|
docker tag hns-login:$tag_num git.woodburn.au/nathanwoodburn/hns-login:$tag_num
|
||||||
|
docker push git.woodburn.au/nathanwoodburn/hns-login:$tag_num
|
||||||
|
docker tag hns-login:$tag_num git.woodburn.au/nathanwoodburn/hns-login:latest
|
||||||
|
docker push git.woodburn.au/nathanwoodburn/hns-login:latest
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
instance/
|
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM python:3.10-bullseye
|
||||||
|
COPY requirements.txt /app/
|
||||||
|
WORKDIR /app
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
COPY . .
|
||||||
|
VOLUME /app/instance
|
||||||
|
CMD ["flask", "run", "-p", "9090", "-h", "0.0.0.0"]
|
@ -1 +1,10 @@
|
|||||||
# varo-openid
|
# varo-openid
|
||||||
|
|
||||||
|
## Add a client
|
||||||
|
Set the following parameters:
|
||||||
|
|
||||||
|
Allowed Scope: `profile`
|
||||||
|
Allowed Grant Types: `authorization_code`
|
||||||
|
Allowed Response Types: `code`
|
||||||
|
Token Endpoint Authentication Method: `client_secret_post`
|
||||||
|
|
||||||
|
9
app.py
Normal file
9
app.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from website.app import create_app
|
||||||
|
|
||||||
|
|
||||||
|
app = create_app({
|
||||||
|
'SECRET_KEY': 'secret',
|
||||||
|
'OAUTH2_REFRESH_TOKEN_GENERATOR': True,
|
||||||
|
'SQLALCHEMY_TRACK_MODIFICATIONS': False,
|
||||||
|
'SQLALCHEMY_DATABASE_URI': 'sqlite:///db.sqlite',
|
||||||
|
})
|
4
requrements.txt
Normal file
4
requrements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Flask
|
||||||
|
Flask-SQLAlchemy
|
||||||
|
Authlib
|
||||||
|
requests
|
36
website/app.py
Normal file
36
website/app.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
from flask import Flask
|
||||||
|
from .models import db
|
||||||
|
from .oauth2 import config_oauth
|
||||||
|
from .routes import bp
|
||||||
|
|
||||||
|
|
||||||
|
def create_app(config=None):
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# load default configuration
|
||||||
|
app.config.from_object('website.settings')
|
||||||
|
|
||||||
|
# load environment configuration
|
||||||
|
if 'WEBSITE_CONF' in os.environ:
|
||||||
|
app.config.from_envvar('WEBSITE_CONF')
|
||||||
|
|
||||||
|
# load app specified configuration
|
||||||
|
if config is not None:
|
||||||
|
if isinstance(config, dict):
|
||||||
|
app.config.update(config)
|
||||||
|
elif config.endswith('.py'):
|
||||||
|
app.config.from_pyfile(config)
|
||||||
|
|
||||||
|
setup_app(app)
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def setup_app(app):
|
||||||
|
|
||||||
|
db.init_app(app)
|
||||||
|
# Create tables if they do not exist already
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
config_oauth(app)
|
||||||
|
app.register_blueprint(bp, url_prefix='')
|
56
website/models.py
Normal file
56
website/models.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import time
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from authlib.integrations.sqla_oauth2 import (
|
||||||
|
OAuth2ClientMixin,
|
||||||
|
OAuth2AuthorizationCodeMixin,
|
||||||
|
OAuth2TokenMixin,
|
||||||
|
)
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String(40), unique=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.username
|
||||||
|
|
||||||
|
def get_user_id(self):
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
def check_password(self, password):
|
||||||
|
return password == 'valid'
|
||||||
|
|
||||||
|
|
||||||
|
class OAuth2Client(db.Model, OAuth2ClientMixin):
|
||||||
|
__tablename__ = 'oauth2_client'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
user_id = db.Column(
|
||||||
|
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'))
|
||||||
|
user = db.relationship('User')
|
||||||
|
|
||||||
|
|
||||||
|
class OAuth2AuthorizationCode(db.Model, OAuth2AuthorizationCodeMixin):
|
||||||
|
__tablename__ = 'oauth2_code'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
user_id = db.Column(
|
||||||
|
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'))
|
||||||
|
user = db.relationship('User')
|
||||||
|
|
||||||
|
|
||||||
|
class OAuth2Token(db.Model, OAuth2TokenMixin):
|
||||||
|
__tablename__ = 'oauth2_token'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
user_id = db.Column(
|
||||||
|
db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'))
|
||||||
|
user = db.relationship('User')
|
||||||
|
|
||||||
|
def is_refresh_token_active(self):
|
||||||
|
if self.revoked:
|
||||||
|
return False
|
||||||
|
expires_at = self.issued_at + self.expires_in * 2
|
||||||
|
return expires_at >= time.time()
|
101
website/oauth2.py
Normal file
101
website/oauth2.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
from authlib.integrations.flask_oauth2 import (
|
||||||
|
AuthorizationServer,
|
||||||
|
ResourceProtector,
|
||||||
|
)
|
||||||
|
from authlib.integrations.sqla_oauth2 import (
|
||||||
|
create_query_client_func,
|
||||||
|
create_save_token_func,
|
||||||
|
create_revocation_endpoint,
|
||||||
|
create_bearer_token_validator,
|
||||||
|
)
|
||||||
|
from authlib.oauth2.rfc6749 import grants
|
||||||
|
from authlib.oauth2.rfc7636 import CodeChallenge
|
||||||
|
from .models import db, User
|
||||||
|
from .models import OAuth2Client, OAuth2AuthorizationCode, OAuth2Token
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
|
||||||
|
TOKEN_ENDPOINT_AUTH_METHODS = [
|
||||||
|
'client_secret_basic',
|
||||||
|
'client_secret_post',
|
||||||
|
'none',
|
||||||
|
]
|
||||||
|
|
||||||
|
def save_authorization_code(self, code, request):
|
||||||
|
code_challenge = request.data.get('code_challenge')
|
||||||
|
code_challenge_method = request.data.get('code_challenge_method')
|
||||||
|
auth_code = OAuth2AuthorizationCode(
|
||||||
|
code=code,
|
||||||
|
client_id=request.client.client_id,
|
||||||
|
redirect_uri=request.redirect_uri,
|
||||||
|
scope=request.scope,
|
||||||
|
user_id=request.user.id,
|
||||||
|
code_challenge=code_challenge,
|
||||||
|
code_challenge_method=code_challenge_method,
|
||||||
|
)
|
||||||
|
db.session.add(auth_code)
|
||||||
|
db.session.commit()
|
||||||
|
return auth_code
|
||||||
|
|
||||||
|
def query_authorization_code(self, code, client):
|
||||||
|
auth_code = OAuth2AuthorizationCode.query.filter_by(
|
||||||
|
code=code, client_id=client.client_id).first()
|
||||||
|
if auth_code and not auth_code.is_expired():
|
||||||
|
return auth_code
|
||||||
|
|
||||||
|
def delete_authorization_code(self, authorization_code):
|
||||||
|
db.session.delete(authorization_code)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def authenticate_user(self, authorization_code):
|
||||||
|
return User.query.get(authorization_code.user_id)
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
|
||||||
|
def authenticate_user(self, username, password):
|
||||||
|
user = User.query.filter_by(username=username).first()
|
||||||
|
if user is not None and user.check_password(password):
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class RefreshTokenGrant(grants.RefreshTokenGrant):
|
||||||
|
def authenticate_refresh_token(self, refresh_token):
|
||||||
|
token = OAuth2Token.query.filter_by(refresh_token=refresh_token).first()
|
||||||
|
if token and token.is_refresh_token_active():
|
||||||
|
return token
|
||||||
|
|
||||||
|
def authenticate_user(self, credential):
|
||||||
|
return User.query.get(credential.user_id)
|
||||||
|
|
||||||
|
def revoke_old_credential(self, credential):
|
||||||
|
credential.revoked = True
|
||||||
|
db.session.add(credential)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
query_client = create_query_client_func(db.session, OAuth2Client)
|
||||||
|
save_token = create_save_token_func(db.session, OAuth2Token)
|
||||||
|
authorization = AuthorizationServer(
|
||||||
|
query_client=query_client,
|
||||||
|
save_token=save_token,
|
||||||
|
)
|
||||||
|
require_oauth = ResourceProtector()
|
||||||
|
|
||||||
|
|
||||||
|
def config_oauth(app):
|
||||||
|
authorization.init_app(app)
|
||||||
|
|
||||||
|
# support all grants
|
||||||
|
authorization.register_grant(grants.ImplicitGrant)
|
||||||
|
authorization.register_grant(grants.ClientCredentialsGrant)
|
||||||
|
authorization.register_grant(AuthorizationCodeGrant, [CodeChallenge(required=True)])
|
||||||
|
authorization.register_grant(PasswordGrant)
|
||||||
|
authorization.register_grant(RefreshTokenGrant)
|
||||||
|
|
||||||
|
# support revocation
|
||||||
|
revocation_cls = create_revocation_endpoint(db.session, OAuth2Token)
|
||||||
|
authorization.register_endpoint(revocation_cls)
|
||||||
|
|
||||||
|
# protect resource
|
||||||
|
bearer_cls = create_bearer_token_validator(db.session, OAuth2Token)
|
||||||
|
require_oauth.register_token_validator(bearer_cls())
|
144
website/routes.py
Normal file
144
website/routes.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import time
|
||||||
|
from .varo_auth import flask_login as varo_auth_flask_login
|
||||||
|
from flask import Blueprint, request, session, url_for
|
||||||
|
from flask import render_template, redirect, jsonify, send_from_directory
|
||||||
|
from werkzeug.security import gen_salt
|
||||||
|
from authlib.integrations.flask_oauth2 import current_token
|
||||||
|
from authlib.oauth2 import OAuth2Error
|
||||||
|
from .models import db, User, OAuth2Client
|
||||||
|
from .oauth2 import authorization, require_oauth
|
||||||
|
|
||||||
|
|
||||||
|
bp = Blueprint('home', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
def current_user():
|
||||||
|
if 'id' in session:
|
||||||
|
uid = session['id']
|
||||||
|
return User.query.get(uid)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def split_by_crlf(s):
|
||||||
|
return [v for v in s.splitlines() if v]
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/', methods=('GET', 'POST'))
|
||||||
|
def home():
|
||||||
|
next_page = request.args.get('next')
|
||||||
|
if request.method == 'POST':
|
||||||
|
auth = varo_auth_flask_login(request)
|
||||||
|
if auth == False:
|
||||||
|
return redirect('/?error=login_failed')
|
||||||
|
print(auth)
|
||||||
|
user = User.query.filter_by(username=auth).first()
|
||||||
|
if not user:
|
||||||
|
user = User(username=auth)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
session['id'] = user.id
|
||||||
|
# if user is not just to log in, but need to head back to the auth page, then go for it
|
||||||
|
if next_page:
|
||||||
|
return redirect(next_page)
|
||||||
|
return redirect('/')
|
||||||
|
user = current_user()
|
||||||
|
if user:
|
||||||
|
clients = OAuth2Client.query.filter_by(user_id=user.id).all()
|
||||||
|
if next_page:
|
||||||
|
return redirect(next_page)
|
||||||
|
else:
|
||||||
|
clients = []
|
||||||
|
|
||||||
|
return render_template('home.html', user=user, clients=clients)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/logout')
|
||||||
|
def logout():
|
||||||
|
del session['id']
|
||||||
|
next = request.args.get('next')
|
||||||
|
if next:
|
||||||
|
return redirect(url_for('home.home', next=next))
|
||||||
|
|
||||||
|
return redirect('/')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/create_client', methods=('GET', 'POST'))
|
||||||
|
def create_client():
|
||||||
|
user = current_user()
|
||||||
|
if not user:
|
||||||
|
return redirect('/')
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template('create_client.html')
|
||||||
|
|
||||||
|
client_id = gen_salt(24)
|
||||||
|
client_id_issued_at = int(time.time())
|
||||||
|
client = OAuth2Client(
|
||||||
|
client_id=client_id,
|
||||||
|
client_id_issued_at=client_id_issued_at,
|
||||||
|
user_id=user.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
form = request.form
|
||||||
|
client_metadata = {
|
||||||
|
"client_name": form["client_name"],
|
||||||
|
"client_uri": form["client_uri"],
|
||||||
|
"grant_types": split_by_crlf(form["grant_type"]),
|
||||||
|
"redirect_uris": split_by_crlf(form["redirect_uri"]),
|
||||||
|
"response_types": split_by_crlf(form["response_type"]),
|
||||||
|
"scope": form["scope"],
|
||||||
|
"token_endpoint_auth_method": form["token_endpoint_auth_method"]
|
||||||
|
}
|
||||||
|
client.set_client_metadata(client_metadata)
|
||||||
|
|
||||||
|
if form['token_endpoint_auth_method'] == 'none':
|
||||||
|
client.client_secret = ''
|
||||||
|
else:
|
||||||
|
client.client_secret = gen_salt(48)
|
||||||
|
|
||||||
|
db.session.add(client)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect('/')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/oauth/authorize', methods=['GET', 'POST'])
|
||||||
|
def authorize():
|
||||||
|
user = current_user()
|
||||||
|
# if user log status is not true (Auth server), then to log it in
|
||||||
|
if not user:
|
||||||
|
return redirect(url_for('home.home', next=request.url))
|
||||||
|
if request.method == 'GET':
|
||||||
|
try:
|
||||||
|
grant = authorization.get_consent_grant(end_user=user)
|
||||||
|
except OAuth2Error as error:
|
||||||
|
return error.error
|
||||||
|
return render_template('authorize.html', user=user, grant=grant)
|
||||||
|
|
||||||
|
grant_user = user
|
||||||
|
|
||||||
|
return authorization.create_authorization_response(grant_user=grant_user)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/oauth/token', methods=['POST'])
|
||||||
|
def issue_token():
|
||||||
|
return authorization.create_token_response()
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/oauth/revoke', methods=['POST'])
|
||||||
|
def revoke_token():
|
||||||
|
return authorization.create_endpoint_response('revocation')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/api/me')
|
||||||
|
@require_oauth('profile')
|
||||||
|
def api_me():
|
||||||
|
user = current_token.user
|
||||||
|
print(user.id, user.username)
|
||||||
|
return jsonify(id=user.id, username=user.username,
|
||||||
|
email="auth+" + user.username + "@hnshosting.au",
|
||||||
|
displayName=user.username+"/")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/favicon.png')
|
||||||
|
def favicon():
|
||||||
|
return send_from_directory('templates', 'favicon.png', mimetype='image/png')
|
0
website/settings.py
Normal file
0
website/settings.py
Normal file
62
website/templates/authorize.html
Normal file
62
website/templates/authorize.html
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>HNS Login</title>
|
||||||
|
<link rel="icon" href="favicon.png" type="image/png">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
/* Dark theme*/
|
||||||
|
background-color: #222;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Authorize Access</h1>
|
||||||
|
<p><strong>{{grant.client.client_name}}</strong> is requesting to access your account.</p>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You are currently logged in as <strong>{{ user.username }}/</strong> (<a href="{{ url_for('.logout', next=request.url) }}">Change Account</a>)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form action="" method="post">
|
||||||
|
<br>
|
||||||
|
<button>Continue</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div style="position: fixed; bottom: 0; width: 100%; text-align: center; background-color: #333; padding: 10px;">
|
||||||
|
Powered by <a href="https://auth.varo.domains/implement" target="_blank">Varo Auth</a> and <a href="nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
101
website/templates/create_client.html
Normal file
101
website/templates/create_client.html
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>HNS Login</title>
|
||||||
|
<link rel="icon" href="favicon.png" type="image/png">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
/* Dark theme*/
|
||||||
|
background-color: #222;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style>
|
||||||
|
label, label > span { display: block; }
|
||||||
|
label { margin: 15px 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="/"><h1>Home</h1></a>
|
||||||
|
<h2>Create OAuth Client</h2>
|
||||||
|
|
||||||
|
<form action="" method="post">
|
||||||
|
<label>
|
||||||
|
<span>Client Name</span>
|
||||||
|
<input type="text" name="client_name">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Client URI</span>
|
||||||
|
<input type="url" name="client_uri">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Allowed Scope</span>
|
||||||
|
<input type="text" name="scope">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Redirect URIs</span>
|
||||||
|
<textarea name="redirect_uri" cols="30" rows="10"></textarea>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Allowed Grant Types</span>
|
||||||
|
<textarea name="grant_type" cols="30" rows="10"></textarea>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Allowed Response Types</span>
|
||||||
|
<textarea name="response_type" cols="30" rows="10"></textarea>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Token Endpoint Auth Method</span>
|
||||||
|
<select name="token_endpoint_auth_method">
|
||||||
|
<option value="client_secret_basic">client_secret_basic</option>
|
||||||
|
<option value="client_secret_post">client_secret_post</option>
|
||||||
|
<option value="none">none</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button>Submit</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div style="height: 5em;"></div>
|
||||||
|
<div style="bottom: 0; text-align: center; background-color: #333; padding: 10px;">
|
||||||
|
Powered by <a href="https://auth.varo.domains/implement" target="_blank">Varo Auth</a> and <a href="nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
website/templates/favicon.png
Normal file
BIN
website/templates/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
119
website/templates/home.html
Normal file
119
website/templates/home.html
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>HNS Login</title>
|
||||||
|
<link rel="icon" href="favicon.png" type="image/png">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
/* Dark theme*/
|
||||||
|
background-color: #222;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
a.button {
|
||||||
|
display: block;
|
||||||
|
width: 200px;
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
button.loginbutton {
|
||||||
|
/* Put in the centre of the screen */
|
||||||
|
|
||||||
|
margin-left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>HNS Login</h1>
|
||||||
|
|
||||||
|
{% if user %}
|
||||||
|
|
||||||
|
<h2>You are currently logged in as <strong>{{ user }}/</strong></h2>
|
||||||
|
|
||||||
|
|
||||||
|
<a href="{{ url_for('.logout') }}" class="button">Log Out</a>
|
||||||
|
|
||||||
|
{% for client in clients %}
|
||||||
|
<pre>
|
||||||
|
<strong>Client Info</strong>
|
||||||
|
{%- for key in client.client_info %}
|
||||||
|
<strong>{{ key }}: </strong>{{ client.client_info[key] }}
|
||||||
|
{%- endfor %}
|
||||||
|
<strong>Client Metadata</strong>
|
||||||
|
{%- for key in client.client_metadata %}
|
||||||
|
<strong>{{ key }}: </strong>{{ client.client_metadata[key] }}
|
||||||
|
{%- endfor %}
|
||||||
|
</pre>
|
||||||
|
<hr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
<p>Want to implement OAuth?<br>
|
||||||
|
Contact Nathan.Woodburn/ on any social media platform</p>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<h2>Login with your Handshake domain</h2>
|
||||||
|
<p>If you have already used Varo Auth, you can just select the domain you want to login with.</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://auth.varo.domains/v1"></script>
|
||||||
|
<script>var varo = new Varo();</script>
|
||||||
|
<button class="loginbutton" onclick='varo.auth().then(auth => {
|
||||||
|
if (auth.success) {
|
||||||
|
// handle success by calling your api to update the users session
|
||||||
|
$.post("/", JSON.stringify(auth.data), (response) => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});'>Login</button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div style="position: fixed; bottom: 0; width: 100%; text-align: center; background-color: #333; padding: 10px;">
|
||||||
|
Powered by <a href="https://auth.varo.domains/implement" target="_blank">Varo Auth</a> and <a href="nathan.woodburn.au" target="_blank">Nathan.Woodburn/</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
22
website/varo_auth.py
Normal file
22
website/varo_auth.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
def flask_login(request):
|
||||||
|
dict = request.form.to_dict()
|
||||||
|
keys = dict.keys()
|
||||||
|
keys = list(keys)[0]
|
||||||
|
keys = json.loads(keys)
|
||||||
|
auth_request = keys['request']
|
||||||
|
return login(auth_request)
|
||||||
|
|
||||||
|
def login(request):
|
||||||
|
r = requests.get(f'https://auth.varo.domains/verify/{request}')
|
||||||
|
r = r.json()
|
||||||
|
if r['success'] == False:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if 'data' in r:
|
||||||
|
data = r['data']
|
||||||
|
if 'name' in data:
|
||||||
|
return data['name']
|
||||||
|
return False
|
Loading…
Reference in New Issue
Block a user