Browse Source

Add basic database, session management.

master
Ben Kurtovic 7 years ago
parent
commit
a8bf24b19a
14 changed files with 191 additions and 13 deletions
  1. +1
    -0
      .gitignore
  2. +3
    -2
      README.md
  3. +17
    -1
      app.py
  4. +36
    -0
      calefaction/auth.py
  5. +9
    -0
      calefaction/config.py
  6. +41
    -0
      calefaction/database.py
  7. +6
    -0
      calefaction/eve/__init__.py
  8. +2
    -2
      calefaction/eve/clock.py
  9. +21
    -0
      calefaction/eve/sso.py
  10. +27
    -4
      config/config.yml.sample
  11. +21
    -0
      data/schema.sql
  12. +1
    -1
      static/main.css
  13. +3
    -2
      templates/_base.mako
  14. +3
    -1
      templates/landing.mako

+ 1
- 0
.gitignore View File

@@ -6,3 +6,4 @@ config/*
data/* data/*


!config/config.yml.sample !config/config.yml.sample
!data/schema.sql

+ 3
- 2
README.md View File

@@ -17,8 +17,9 @@ Guide


### Setup ### Setup


cp config.yml.sample config.yml
vim config.yml
cp config/config.yml.sample config/config.yml
vim config/config.yml # follow instructions
cat data/schema.sql | sqlite3 data/db.sqlite3
... ...


### Test ### Test


+ 17
- 1
app.py View File

@@ -5,30 +5,46 @@ from pathlib import Path


from flask import Flask, g from flask import Flask, g
from flask_mako import MakoTemplates, render_template from flask_mako import MakoTemplates, render_template
from werkzeug.local import LocalProxy


import calefaction import calefaction
from calefaction.auth import AuthManager
from calefaction.config import Config from calefaction.config import Config
from calefaction.database import Database
from calefaction.eve import EVE from calefaction.eve import EVE
from calefaction.util import catch_errors, set_up_hash_versioning from calefaction.util import catch_errors, set_up_hash_versioning


basepath = Path(__file__).resolve().parent
app = Flask(__name__) app = Flask(__name__)

basepath = Path(__file__).resolve().parent
config = Config(basepath / "config") config = Config(basepath / "config")
Database.path = str(basepath / "data" / "db.sqlite3")
eve = EVE() eve = EVE()
auth = AuthManager(config, eve)


MakoTemplates(app) MakoTemplates(app)
set_up_hash_versioning(app) set_up_hash_versioning(app)
config.install(app)


@app.before_request @app.before_request
def prepare_request(): def prepare_request():
g.auth = auth
g.config = config g.config = config
g.eve = eve g.eve = eve
g.version = calefaction.__version__ g.version = calefaction.__version__


app.before_request(Database.pre_hook)
app.teardown_appcontext(Database.post_hook)

@app.route("/") @app.route("/")
@catch_errors(app) @catch_errors(app)
def index(): def index():
return render_template("landing.mako") return render_template("landing.mako")


@app.route("/login")
@catch_errors(app)
def login():
return "login" # ...

if __name__ == "__main__": if __name__ == "__main__":
app.run(debug=True, port=8080) app.run(debug=True, port=8080)

+ 36
- 0
calefaction/auth.py View File

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-

from flask import g, session, url_for
from itsdangerous import URLSafeSerializer

__all__ = ["AuthManager"]

_SCOPES = ["publicData", "characterAssetsRead"] # ...

class AuthManager:

def __init__(self, config, eve):
self._config = config
self._eve = eve

def _new_session_id(self):
with g.db as conn:
cur = conn.execute("INSERT INTO session DEFAULT VALUES")
return cur.lastrowid

def get_session_id(self):
if "id" not in session:
session["id"] = self._new_session_id()
return session["id"]

def get_state_hash(self):
key = self._config.get("auth.session_key")
serializer = URLSafeSerializer(key)
return serializer.dumps(self.get_session_id())

def make_login_link(self):
cid = self._config.get("auth.client_id")
target = url_for("login", _external=True, _scheme=self._config.scheme)
scopes = _SCOPES
state = self.get_state_hash()
return self._eve.sso.get_authorize_url(cid, target, scopes, state)

+ 9
- 0
calefaction/config.py View File

@@ -22,3 +22,12 @@ class Config:
return default return default
obj = obj[item] obj = obj[item]
return obj return obj

@property
def scheme(self):
return "https" if self.get("site.https") else "http"

def install(self, app):
app.config["SERVER_NAME"] = self.get("site.canonical")
app.config["PREFERRED_URL_SCHEME"] = self.scheme
app.secret_key = self.get("auth.session_key")

+ 41
- 0
calefaction/database.py View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-

import sqlite3

from flask import g
from werkzeug.local import LocalProxy

__all__ = ["Database"]

class Database:
path = None

def __init__(self):
if self.path is None:
raise RuntimeError("Database.path not set")
self._conn = sqlite3.connect(self.path)
import traceback

def __enter__(self):
return self._conn.__enter__()

def __exit__(self, exc_type, exc_value, trace):
return self._conn.__exit__(exc_type, exc_value, trace)

@classmethod
def _get(cls):
if not hasattr(g, "_db"):
g._db = cls()
return g._db

@classmethod
def pre_hook(cls):
g.db = LocalProxy(cls._get)

@classmethod
def post_hook(cls, exc):
if hasattr(g, "_db"):
g._db.close()

def close(self):
return self._conn.close()

+ 6
- 0
calefaction/eve/__init__.py View File

@@ -2,6 +2,7 @@


from .clock import Clock from .clock import Clock
from .image import ImageServer from .image import ImageServer
from .sso import SSOManager


__all__ = ["EVE"] __all__ = ["EVE"]


@@ -10,6 +11,7 @@ class EVE:
def __init__(self): def __init__(self):
self._clock = Clock() self._clock = Clock()
self._image = ImageServer() self._image = ImageServer()
self._sso = SSOManager()


@property @property
def clock(self): def clock(self):
@@ -18,3 +20,7 @@ class EVE:
@property @property
def image(self): def image(self):
return self._image return self._image

@property
def sso(self):
return self._sso

+ 2
- 2
calefaction/eve/clock.py View File

@@ -4,10 +4,10 @@ from datetime import datetime


__all__ = ["Clock"] __all__ = ["Clock"]


YEAR_DELTA = 1898
_YEAR_DELTA = 1898


class Clock: class Clock:


def now(self): def now(self):
dt = datetime.utcnow() dt = datetime.utcnow()
return str(dt.year - YEAR_DELTA) + dt.strftime("-%m-%d %H:%M")
return str(dt.year - _YEAR_DELTA) + dt.strftime("-%m-%d %H:%M")

+ 21
- 0
calefaction/eve/sso.py View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-

from urllib.parse import urlencode

__all__ = ["SSOManager"]

class SSOManager:

def get_authorize_url(self, client_id, redirect_uri, scopes=None,
state=None):
baseurl = "https://login.eveonline.com/oauth/authorize?"
params = {
"response_type": "code",
"redirect_uri": redirect_uri,
"client_id": client_id
}
if scopes:
params["scope"] = " ".join(scopes)
if state is not None:
params["state"] = state
return baseurl + urlencode(params)

+ 27
- 4
config/config.yml.sample View File

@@ -2,14 +2,37 @@
# Copy this to config.yml and modify it to set up your website. # Copy this to config.yml and modify it to set up your website.
# You must restart the server after making any changes. # You must restart the server after making any changes.


site:
# Full canonical server name; include port if not default:
canonical: example.com
# Assume HTTPS? This affects how URLs are generated, not how the site is
# served (setting up TLS is your responsibility):
https: yes

corp: corp:
# Find your corp's ID at, e.g., https://zkillboard.com/corporation/917701062/
# You need to reset the database if this value is changed in the future.
# Find your corp's ID at e.g. https://zkillboard.com/corporation/917701062/:
id: 123456789 id: 123456789
# Full corp name (doesn't need to match in-game name exactly, but it should)
# Full corp name (doesn't need to match in-game name exactly, but it should):
name: My Corp Name Here name: My Corp Name Here


# Default stylesheet from static/styles/*.css:
# one of "amarr", "caldari", "gallente", "minmatar", or add your own
auth:
# Secure session signing key. Never share with anyone. Can generate with
# "import base64, os; base64.b64encode(os.urandom(24))":
session_key: sEQMbNbxRxHBhyGtt8cuLEMN6sDM1JcP
# You need to create an application at
# https://developers.eveonline.com/applications for this corp's website.
# Set the callback URL to http(s)://<your domain>/login (match the protocol
# with "site.https" above) and the scopes to:
# - publicData
# - ...
# SSO client ID:
client_id: a290afea820b8dd8c46d3883898ab66d
# SSO client secret:
client_secret: XXAPGc0LM6wdOJAwSNQmliZ2QhQpoBuUutQY6Rlc

# Default stylesheet from static/styles/*.css;
# one of "amarr", "caldari", "gallente", "minmatar", or create your own:
style: null style: null


welcome: |- welcome: |-


+ 21
- 0
data/schema.sql View File

@@ -0,0 +1,21 @@
-- Schema for Calefaction's internal database

DROP TABLE IF EXISTS session;

CREATE TABLE session (
session_id INTEGER PRIMARY KEY AUTOINCREMENT,
session_character INTEGER DEFAULT 0,
session_touched TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

DROP TABLE IF EXISTS character;

CREATE TABLE character (
character_id INTEGER PRIMARY KEY,
character_name TEXT,
character_token BLOB,
character_refresh BLOB,
character_token_expiry TIMESTAMP,
character_last_verify TIMESTAMP,
character_style TEXT
);

+ 1
- 1
static/main.css View File

@@ -34,7 +34,7 @@ a:hover {
} }


main, header, footer { main, header, footer {
background-color: rgba(0, 0, 0, 0.75);
background-color: rgba(0, 0, 0, 0.8);
border-color: #4A4A4A; border-color: #4A4A4A;
} }




+ 3
- 2
templates/_base.mako View File

@@ -6,6 +6,7 @@
<%block name="title">${g.config.get("corp.name") | h}</%block> <%block name="title">${g.config.get("corp.name") | h}</%block>
</title> </title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="canonical" href="${g.config.scheme}://${g.config.get('site.canonical')}${request.script_root}${request.path}">
<link rel="stylesheet" type="text/css" href="${url_for('staticv', filename='main.css')}"/> <link rel="stylesheet" type="text/css" href="${url_for('staticv', filename='main.css')}"/>
% if g.config.get("style"): % if g.config.get("style"):
<% stylesheet = "styles/{}.css".format(g.config.get("style")) %> <% stylesheet = "styles/{}.css".format(g.config.get("style")) %>
@@ -21,10 +22,10 @@
<div> <div>
<div class="left"> <div class="left">
<%block name="lefthead"> <%block name="lefthead">
<a href="/">
<a href="${url_for('index')}">
<img id="corp-masthead" class="aligned" title="Home" alt="Home" src="${g.eve.image.corp(g.config.get('corp.id'), 256)}"/> <img id="corp-masthead" class="aligned" title="Home" alt="Home" src="${g.eve.image.corp(g.config.get('corp.id'), 256)}"/>
</a> </a>
<a href="/" class="aligned">${g.config.get("corp.name") | h}</a>
<a href="${url_for('index')}" class="aligned">${g.config.get("corp.name") | h}</a>
</%block> </%block>
</div> </div>
<div class="right"> <div class="right">


+ 3
- 1
templates/landing.mako View File

@@ -1,6 +1,8 @@
<%inherit file="_base.mako"/> <%inherit file="_base.mako"/>
<%block name="righthead"> <%block name="righthead">
<img id="login-button" class="aligned" src="${url_for('staticv', filename='images/eve-login.png')}"/>
<a href="${g.auth.make_login_link()}">
<img id="login-button" class="aligned" title="Log in with EVE Online" alt="Log in with EVE Online" src="${url_for('staticv', filename='images/eve-login.png')}"/>
</a>
</%block> </%block>
<div id="welcome"> <div id="welcome">
% for paragraph in g.config.get("welcome").split("\n\n"): % for paragraph in g.config.get("welcome").split("\n\n"):


Loading…
Cancel
Save