Parcourir la source

Add basic database, session management.

master
Ben Kurtovic il y a 8 ans
Parent
révision
a8bf24b19a
14 fichiers modifiés avec 191 ajouts et 13 suppressions
  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 Voir le fichier

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

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

+ 3
- 2
README.md Voir le fichier

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

### 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


+ 17
- 1
app.py Voir le fichier

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

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

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

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

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

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

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

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

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

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

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

+ 36
- 0
calefaction/auth.py Voir le fichier

@@ -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 Voir le fichier

@@ -22,3 +22,12 @@ class Config:
return default
obj = obj[item]
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 Voir le fichier

@@ -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 Voir le fichier

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

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

__all__ = ["EVE"]

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

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

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

+ 2
- 2
calefaction/eve/clock.py Voir le fichier

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

__all__ = ["Clock"]

YEAR_DELTA = 1898
_YEAR_DELTA = 1898

class Clock:

def now(self):
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 Voir le fichier

@@ -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 Voir le fichier

@@ -2,14 +2,37 @@
# Copy this to config.yml and modify it to set up your website.
# 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:
# 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
# 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

# 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

welcome: |-


+ 21
- 0
data/schema.sql Voir le fichier

@@ -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 Voir le fichier

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

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



+ 3
- 2
templates/_base.mako Voir le fichier

@@ -6,6 +6,7 @@
<%block name="title">${g.config.get("corp.name") | h}</%block>
</title>
<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')}"/>
% if g.config.get("style"):
<% stylesheet = "styles/{}.css".format(g.config.get("style")) %>
@@ -21,10 +22,10 @@
<div>
<div class="left">
<%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)}"/>
</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>
</div>
<div class="right">


+ 3
- 1
templates/landing.mako Voir le fichier

@@ -1,6 +1,8 @@
<%inherit file="_base.mako"/>
<%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>
<div id="welcome">
% for paragraph in g.config.get("welcome").split("\n\n"):


Chargement…
Annuler
Enregistrer