From 2ea8f79dd985fcf39a52422b01056d932c7396b9 Mon Sep 17 00:00:00 2001 From: Wojciech Kwolek Date: Wed, 17 Nov 2021 20:33:39 +0000 Subject: [PATCH] add APIKey model --- app/db.py | 35 +++++++++++++++++++++++++++ migrations/versions/a20adaee67da_.py | 36 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 migrations/versions/a20adaee67da_.py diff --git a/app/db.py b/app/db.py index e7c2a21..d465978 100644 --- a/app/db.py +++ b/app/db.py @@ -1,3 +1,5 @@ +import secrets +from typing import Optional, Tuple from sqlalchemy.sql import func from flask import Flask from flask_sqlalchemy import SQLAlchemy @@ -28,6 +30,9 @@ class User(UserMixin, db.Model): accomplishments = db.relationship( 'Accomplishment', backref='user', lazy=True) + api_keys = db.relationship( + 'APIKey', backref='user', lazy=True) + # TODO: set user timezone from geoip on registration timezone = db.Column(db.String(64), nullable=False) start_of_day = db.Column(db.Integer, nullable=False) @@ -96,3 +101,33 @@ class Accomplishment(db.Model): def get_today_total(user): today = Day.today(user) return Accomplishment.get_day_total(user, today) + + +class APIKey(db.Model): + id = db.Column(db.Integer, primary_key=True) + app_name = db.Column(db.String(64), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + hash = db.Column(db.String(128), nullable=False) + + @staticmethod + def create(user: User, app: str) -> Tuple['APIKey', str]: + key = secrets.token_urlsafe(nbytes=48) + return APIKey( + app_name=app, + user_id=user.id, + hash=generate_password_hash(key) + ), f"{app}:{user.id}:{key}" + + def check(self, key: str) -> bool: + app, user_id, key = key.split(":", 2) + return self.user_id == int(user_id) and self.app_name == app and check_password_hash(self.hash, key) + + @classmethod + def find(cls, key: str) -> Optional[User]: + app, user_id, _ = key.split(":", 2) + results = cls.query.filter( + cls.app_name == app, cls.user_id == int(user_id)) + for result in results: + if result.check(key): + return result.user + return None diff --git a/migrations/versions/a20adaee67da_.py b/migrations/versions/a20adaee67da_.py new file mode 100644 index 0000000..8a8128f --- /dev/null +++ b/migrations/versions/a20adaee67da_.py @@ -0,0 +1,36 @@ +"""Add api keys + +Revision ID: a20adaee67da +Revises: 687170684a50 +Create Date: 2021-11-17 20:21:01.069492 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a20adaee67da' +down_revision = '687170684a50' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('api_key', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('app_name', sa.String( + length=64), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('hash', sa.String(length=128), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('api_key') + # ### end Alembic commands ###