mirror of
https://github.com/TitanEmbeds/Titan.git
synced 2024-12-23 21:47:04 +01:00
Basic dashboard support for Authenticated users
This commit is contained in:
parent
11c326b35d
commit
2e5f6fafbf
26
run.py
26
run.py
@ -3,5 +3,31 @@ from titanembeds.app import app
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
from flask import jsonify, request
|
||||
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' # Testing oauthlib
|
||||
|
||||
# Session viewer https://gist.github.com/babldev/502364a3f7c9bafaa6db
|
||||
def decode_flask_cookie(secret_key, cookie_str):
|
||||
import hashlib
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
from flask.sessions import TaggedJSONSerializer
|
||||
salt = 'cookie-session'
|
||||
serializer = TaggedJSONSerializer()
|
||||
signer_kwargs = {
|
||||
'key_derivation': 'hmac',
|
||||
'digest_method': hashlib.sha1
|
||||
}
|
||||
s = URLSafeTimedSerializer(secret_key, salt=salt, serializer=serializer, signer_kwargs=signer_kwargs)
|
||||
return s.loads(cookie_str)
|
||||
|
||||
@app.route("/session")
|
||||
def session():
|
||||
cookie = request.cookies.get('session')
|
||||
if cookie:
|
||||
decoded = decode_flask_cookie(app.secret_key, request.cookies.get('session'))
|
||||
else:
|
||||
decoded = None
|
||||
return jsonify(session_cookie=decoded)
|
||||
|
||||
|
||||
app.run(host="0.0.0.0",port=3000,debug=True)
|
||||
|
@ -2,7 +2,8 @@ session:
|
||||
- unauthenticated T/F
|
||||
- user_id - random generated 4 digit discrim upon nick creation, otherwise the discord client id if authed
|
||||
- username
|
||||
- user_keys {guildid: key, ...} - unused if authed
|
||||
- avatar
|
||||
- user_keys {guildid: key, ...} - replaced with discord token dict if authed
|
||||
|
||||
database:
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
from config import config
|
||||
from database import db
|
||||
from flask import Flask, render_template, request, session, url_for, redirect
|
||||
from flask import Flask, render_template, request, session, url_for, redirect, jsonify
|
||||
import blueprints.api
|
||||
import blueprints.user
|
||||
import os
|
||||
|
||||
|
||||
os.chdir(config['app-location'])
|
||||
app = Flask(__name__)
|
||||
app = Flask(__name__, static_folder="static")
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = config['database-uri']
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # Suppress the warning/no need this on for now.
|
||||
app.secret_key = config['app-secret']
|
||||
@ -27,14 +27,11 @@ def post_set_username(guildid, channelid):
|
||||
return redirect(url_for("embed_get", guildid=guildid, channelid=channelid))
|
||||
|
||||
@app.route("/")
|
||||
def hello():
|
||||
return "This page is not blank"
|
||||
def index():
|
||||
return render_template("index.html.jinja2")
|
||||
|
||||
@app.route("/embed/<guildid>/<channelid>")
|
||||
def embed_get(guildid, channelid):
|
||||
if 'username' not in session:
|
||||
return redirect(url_for("get_set_username", guildid=guildid, channelid=channelid))
|
||||
return render_template("embed.html")
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0",port=3000,debug=True)
|
||||
|
@ -1,5 +1,5 @@
|
||||
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans
|
||||
from titanembeds.decorators import valid_session_required
|
||||
from titanembeds.database import db, Guilds, UnauthenticatedUsers, UnauthenticatedBans, AuthenticatedUsers
|
||||
from titanembeds.decorators import valid_session_required, discord_users_only
|
||||
from titanembeds.discordrest import DiscordREST
|
||||
from flask import Blueprint, abort, jsonify, session, request
|
||||
from sqlalchemy import and_
|
||||
@ -141,8 +141,21 @@ def create_unauthenticated_user():
|
||||
status = {'banned': True}
|
||||
return jsonify(status=status)
|
||||
|
||||
@api.route("/new_guild", methods=["POST"])
|
||||
@discord_users_only(api=True)
|
||||
def post_new_guild():
|
||||
pass
|
||||
|
||||
@api.route("/query_guild", methods=["GET"])
|
||||
@valid_session_required(api=True)
|
||||
def query_guild():
|
||||
guild_id = request.args.get('guild_id')
|
||||
return jsonify(exists=check_guild_existance(guild_id))
|
||||
|
||||
@api.route("/check_discord_authentication", methods=["GET"])
|
||||
@discord_users_only(api=True)
|
||||
def check_discord_authentication():
|
||||
if not session['unauthenticated']:
|
||||
return jsonify(error=False)
|
||||
else:
|
||||
return jsonify(error=True)
|
||||
|
@ -1,35 +1,64 @@
|
||||
from flask import Blueprint, request, redirect, jsonify, abort, session
|
||||
from flask import Blueprint, request, redirect, jsonify, abort, session, url_for, render_template
|
||||
from requests_oauthlib import OAuth2Session
|
||||
from config import config
|
||||
from titanembeds.decorators import discord_users_only
|
||||
|
||||
user = Blueprint("user", __name__)
|
||||
redirect_url = config['app-base-url'] + "/user/callback"
|
||||
authorize_url = "https://discordapp.com/api/oauth2/authorize"
|
||||
token_url = "https://discordapp.com/api/oauth2/token"
|
||||
avatar_base_url = "https://cdn.discordapp.com/avatars/"
|
||||
guild_icon_url = "https://cdn.discordapp.com/icons/"
|
||||
|
||||
def make_session(token=None, state=None, scope=None):
|
||||
def make_authenticated_session(token=None, state=None, scope=None):
|
||||
return OAuth2Session(
|
||||
client_id=config['client-id'],
|
||||
token=token,
|
||||
state=state,
|
||||
scope=scope,
|
||||
redirect_uri=redirect_url,
|
||||
redirect_uri=request.url_root + "user/callback",
|
||||
)
|
||||
|
||||
def get_current_user():
|
||||
token = session['discord_token']
|
||||
discord = make_session(token=token)
|
||||
req = discord.get("https://discordapp.com/api/users/@me")
|
||||
def discordrest_from_user(endpoint):
|
||||
token = session['user_keys']
|
||||
discord = make_authenticated_session(token=token)
|
||||
req = discord.get("https://discordapp.com/api/v6{}".format(endpoint))
|
||||
return req
|
||||
|
||||
def get_current_authenticated_user():
|
||||
req = discordrest_from_user("/users/@me")
|
||||
if req.status_code != 200:
|
||||
abort(req.status_code)
|
||||
user = req.json()
|
||||
return user
|
||||
|
||||
def user_has_permission(permission, index):
|
||||
return bool((int(permission) >> index) & 1)
|
||||
|
||||
def get_user_guilds():
|
||||
req = discordrest_from_user("/users/@me/guilds")
|
||||
return req
|
||||
|
||||
def get_user_managed_servers():
|
||||
guilds = get_user_guilds().json()
|
||||
filtered = []
|
||||
for guild in guilds:
|
||||
permission = guild['permissions'] # Manage Server, Ban Members, Kick Members
|
||||
if guild['owner'] or user_has_permission(permission, 5) or user_has_permission(permission, 2) or user_has_permission(permission, 1):
|
||||
filtered.append(guild)
|
||||
return filtered
|
||||
|
||||
def generate_avatar_url(id, av):
|
||||
return avatar_base_url + str(id) + '/' + str(av) + '.jpg'
|
||||
|
||||
def generate_guild_icon_url(id, hash):
|
||||
return guild_icon_url + str(id) + "/" + str(hash) + ".jpg"
|
||||
|
||||
@user.route("/login_authenticated", methods=["GET"])
|
||||
def login_authenticated():
|
||||
session["redirect"] = request.args.get("redirect")
|
||||
scope = ['identify', 'guilds', 'guilds.join']
|
||||
discord = make_session(scope=scope)
|
||||
discord = make_authenticated_session(scope=scope)
|
||||
authorization_url, state = discord.authorization_url(
|
||||
authorize_url,
|
||||
access_type="offline"
|
||||
@ -41,27 +70,40 @@ def login_authenticated():
|
||||
def callback():
|
||||
state = session.get('oauth2_state')
|
||||
if not state or request.values.get('error'):
|
||||
return "state error"
|
||||
discord = make_session(state=state)
|
||||
return redirect(url_for('user.logout'))
|
||||
discord = make_authenticated_session(state=state)
|
||||
discord_token = discord.fetch_token(
|
||||
token_url,
|
||||
client_secret=config['client-secret'],
|
||||
authorization_response=request.url)
|
||||
if not discord_token:
|
||||
return "no discord token"
|
||||
session['discord_token'] = discord_token
|
||||
return str(discord_token)
|
||||
return redirect(url_for('user.logout'))
|
||||
session['user_keys'] = discord_token
|
||||
session['unauthenticated'] = False
|
||||
user = get_current_authenticated_user()
|
||||
session['user_id'] = user['id']
|
||||
session['username'] = user['username']
|
||||
session['avatar'] = generate_avatar_url(user['id'], user['avatar'])
|
||||
if session["redirect"]:
|
||||
return redirect(session["redirect"])
|
||||
return redirect(url_for("user.dashboard"))
|
||||
|
||||
@user.route('/logout', methods=["GET"])
|
||||
def logout():
|
||||
session.clear()
|
||||
return "logged out"
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@user.route("/dashboard")
|
||||
@discord_users_only()
|
||||
def dashboard():
|
||||
return render_template("dashboard.html.jinja2", servers=get_user_managed_servers(), icon_generate=generate_guild_icon_url)
|
||||
|
||||
@user.route("/administrate_guild/<guild_id>")
|
||||
@discord_users_only()
|
||||
def administrate_guild(guild_id):
|
||||
return str(guild_id)
|
||||
|
||||
@user.route('/me')
|
||||
@discord_users_only()
|
||||
def me():
|
||||
return jsonify(user=get_current_user())
|
||||
|
||||
@user.route('/avatar')
|
||||
def avatar():
|
||||
user = get_current_user()
|
||||
return avatar_base_url + str(user['id']) + '/' + str(user['avatar']) + '.jpg'
|
||||
return jsonify(user=get_current_authenticated_user())
|
||||
|
@ -1,17 +1,28 @@
|
||||
from functools import wraps
|
||||
from flask import url_for, redirect, session, jsonify
|
||||
from flask import url_for, redirect, session, jsonify, abort
|
||||
|
||||
def valid_session_required(api=False):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if 'unauthenticated' not in session or 'user_id' not in session or 'username' not in session:
|
||||
session.clear()
|
||||
if api:
|
||||
return jsonify(error=True, message="Unauthenticated session"), 403
|
||||
redirect(url_for('index'))
|
||||
redirect(url_for('user.logout'))
|
||||
if session['unauthenticated'] and 'user_keys' not in session:
|
||||
session['user_keys'] = {}
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
return decorator
|
||||
|
||||
def discord_users_only(api=False):
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if 'unauthenticated' not in session or session['unauthenticated']:
|
||||
if api:
|
||||
return jsonify(error=True, message="Not logged in as a discord user"), 403
|
||||
return redirect(url_for("user.login_authenticated"))
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
return decorator
|
||||
|
@ -105,6 +105,11 @@ class DiscordREST:
|
||||
# Guild
|
||||
#####################
|
||||
|
||||
def get_guild(self, guild_id):
|
||||
_endpoint = "/guilds/{guild_id}".format(guild_id=guild_id)
|
||||
r = self.request("GET", _endpoint)
|
||||
return r
|
||||
|
||||
def get_guild_channels(self, guild_id):
|
||||
_endpoint = "/guilds/{guild_id}/channels".format(guild_id=guild_id)
|
||||
r = self.request("GET", _endpoint)
|
||||
|
52
titanembeds/static/css/style.css
Normal file
52
titanembeds/static/css/style.css
Normal file
@ -0,0 +1,52 @@
|
||||
html {
|
||||
background-color: #7986cb;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: #3f51b5;
|
||||
background: linear-gradient(rgba(63, 81, 181, 1), rgba(255,0,0,0));
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.page-footer {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
nav .brand-logo {
|
||||
left: 10%;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #303f9f;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #3f51b5;
|
||||
}
|
||||
|
||||
.btn:focus {
|
||||
background-color: #536dfe;
|
||||
}
|
||||
|
||||
.avatar_menu {
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.center_content {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
32
titanembeds/templates/dashboard.html.jinja2
Normal file
32
titanembeds/templates/dashboard.html.jinja2
Normal file
@ -0,0 +1,32 @@
|
||||
{% extends 'site_layout.html.jinja2' %}
|
||||
{% block title %}Dashboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>User Dashboard</h1>
|
||||
<p class="flow-text">Select a server to configure Titan Embeds.</p>
|
||||
<p>*List missing some servers? It's because you must have either <strong>Manage Server</strong>, <strong>Kick Members</strong>, or <strong>Ban Members</strong> permissions to modify embed settings.</p>
|
||||
<div class="row">
|
||||
{% for server in servers %}
|
||||
<div class="col s6">
|
||||
<div class="card-panel grey lighten-5 z-depth-1">
|
||||
<div class="row valign-wrapper">
|
||||
<div class="col s2">
|
||||
{% if server.icon %}
|
||||
<img src="{{ icon_generate(server.id, server.icon) }}" alt="" class="circle responsive-img">
|
||||
{% else %}
|
||||
<span class="black-text">No icon :(</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col s10">
|
||||
<span class="black-text">
|
||||
<p class="flow-text">{{ server.name }}</p>
|
||||
<br>
|
||||
<a class="waves-effect waves-light btn" href="{{url_for('user.administrate_guild', guild_id=server['id'])}}">Modify</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
17
titanembeds/templates/index.html.jinja2
Normal file
17
titanembeds/templates/index.html.jinja2
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends 'site_layout.html.jinja2' %}
|
||||
{% block title %}Index{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="center-align">Embed Discord like a<br><strong>true Titan</strong></h1>
|
||||
<p class="flow-text center-align">Add <strong>Titan</strong> to your discord server to create your own personalized chat embed!</p>
|
||||
<a class="waves-effect waves-light btn btn-large center_content" href="{{ url_for('user.dashboard') }}">Start here!</a>
|
||||
<br /><br />
|
||||
<div style="display: flex;align-items: center;">
|
||||
<video preload="true" loop="" style="width:100%; border-radius: 10px;">
|
||||
<source src="http://mee6.xyz/static/mee6.mp4" type="video/mp4">
|
||||
<source src="http://mee6.xyz/static/mee6.webm" type="video/webm; codecs=vp8, vorbis">
|
||||
<source type="video/ogg; codecs=theora, vorbis" src="http://mee6.xyz/static/mee6.ogg">
|
||||
Your browser does not support the video tag.
|
||||
</video> <!-- TODO: Fix video and add autoplay -->
|
||||
</div>
|
||||
{% endblock %}
|
55
titanembeds/templates/site_layout.html.jinja2
Normal file
55
titanembeds/templates/site_layout.html.jinja2
Normal file
@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--Import Google Icon Font-->
|
||||
<link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<!--Import materialize.css-->
|
||||
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.1/css/materialize.min.css" integrity="sha256-6DQKO56c9MZL0LAc7QNtxqJyqSa3rS9Gq5FVcIhtA+w=" crossorigin="anonymous" media="screen,projection"/>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
|
||||
<!--Let browser know website is optimized for mobile-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
|
||||
<title>{% block title %}{% endblock %} - Titan Embeds for Discord</title>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
{% if session['unauthenticated'] is defined and not session['unauthenticated'] %}
|
||||
<ul id="menu_dropdown" class="dropdown-content">
|
||||
<li><a href="{{ url_for('user.dashboard') }}">Dashboard</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="{{ url_for('user.logout') }}">Logout</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<nav>
|
||||
<div class="nav-wrapper container">
|
||||
<a href="/" class="brand-logo"><b>Titan</b>Embeds</a>
|
||||
<ul id="nav-mobile" class="right">
|
||||
<li><a href="#" class="waves-effect btn z-depth-3">Visit Us!</a></li> <!-- TODO: Add discord guild invite -->
|
||||
{% if session['unauthenticated'] is defined and not session['unauthenticated'] %}
|
||||
<li><a id="menu_drop" data-activates="menu_dropdown" class="waves-effect btn z-depth-3 btn-floating dropdown-button avatar_menu" style='background-image: url(" {{ session['avatar'] }} ")'></a></li>
|
||||
{% else %}
|
||||
<li><a href="{{ url_for('user.login_authenticated') }}" class="waves-effect btn z-depth-3">Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
<footer class="page-footer">
|
||||
<div class="footer-copyright">
|
||||
<div class="container">
|
||||
A project by EndenDragon
|
||||
<a class="grey-text text-lighten-4 right" href="https://github.com/EndenDragon/Titan">GitHub Repo</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!--Import jQuery before materialize.js-->
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.1/js/materialize.min.js" integrity="sha256-ToPQhpo/E89yaCd7+V8LUCjobNRkjilRXfho6x3twLU=" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user