Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Dockerfile-app
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ ADD ./celery_task/__init__.py \
./celery_task/

ADD ./models/__init__.py \
./models/api_token.py \
./models/base.py \
./models/index.py \
./models/subscriberdb.py \
./models/

ADD ./module/__init__.py \
./module/api_token.py \
./module/awsses.py \
./module/ga.py \
./module/sender.py \
Expand All @@ -37,6 +39,7 @@ ADD ./templates/admin_subscriber_add.html \
./templates/base_subscribe.html \
./templates/base.html \
./templates/index.html \
./templates/settings_token.html \
./templates/subscribe_coscup.html \
./templates/subscriber_error.html \
./templates/subscriber_intro.html \
Expand All @@ -49,5 +52,7 @@ ADD ./view/__init__.py \
./view/reader.py \
./view/subscribe.py \
./view/subscriber.py \
./view/token.py \
./view/trello.py \
./view/volunteer.py \
./view/
17 changes: 16 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import google_auth_oauthlib.flow
from apiclient import discovery
from flask import (Flask, g, got_request_exception, redirect, render_template,
request, session, url_for)
request, session, url_for, make_response)
from flask.wrappers import Response
from werkzeug.wrappers import Response as ResponseBase

Expand All @@ -21,6 +21,9 @@
from view.subscribe import VIEW_SUBSCRIBE
from view.subscriber import VIEW_SUBSCRIBER
from view.trello import VIEW_TRELLO
from view.volunteer import VIEW_VOLUNTEER
from view.token import VIEW_TOKEN
from module.api_token import APIToken

logging.basicConfig(
filename='./log/log.log',
Expand All @@ -37,7 +40,9 @@
app.register_blueprint(VIEW_READER)
app.register_blueprint(VIEW_SUBSCRIBE)
app.register_blueprint(VIEW_SUBSCRIBER)
app.register_blueprint(VIEW_TOKEN)
app.register_blueprint(VIEW_TRELLO)
app.register_blueprint(VIEW_VOLUNTEER)

if app.debug:
app.config['TEMPLATES_AUTO_RELOAD'] = True
Expand All @@ -60,6 +65,16 @@ def need_login() -> ResponseBase | None:
request.headers.get('USER-AGENT'),
session, )

if request.path.startswith('/volunteer'):
token = request.headers.get('Authorization')
if token is None:
return make_response('Unauthorized', 401)

if APIToken.verify(token) is True:
return None

return make_response('Unauthorized', 401)

Comment on lines +68 to +77
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@orertrr 這段移到 view/volunteer.py 的每一個 endpoint 去判斷,這裡想要單純的扮演 route 的節點。然後在 Line 78 加入 and not request.path.startswith('/volunteer')

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

這部分如果包成 function 然後掛一個 VIEW_VOLUNTEER.before_request 的 decorator 上去可以嗎?

理論上這樣寫可以有同樣的效果,且之後 /volunteer 底下有要再加 Endpoint 時也不用再寫同樣的邏輯

if request.path not in NO_NEED_LOGIN_PATH \
and not request.path.startswith('/subscriber') \
and not request.path.startswith('/subscribe') \
Expand Down
19 changes: 19 additions & 0 deletions models/api_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
''' APIToken DB '''
from models.base import DBBase

class APITokenDB(DBBase):
''' Token Collection

Schema:
{
serial_no: string,
token: string,
label: string
}
'''
def __init__(self) -> None:
super().__init__('api_token')

def index(self) -> None:
''' index '''
self.create_index([('serial_no', 1)])
2 changes: 2 additions & 0 deletions models/index.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
''' index '''
from models.api_token import APITokenDB
from models.subscriberdb import (SubscriberDB, SubscriberLoginTokenDB,
SubscriberReadDB)

if __name__ == '__main__':
APITokenDB().index()
SubscriberDB().index()
SubscriberLoginTokenDB().index()
SubscriberReadDB().index()
62 changes: 62 additions & 0 deletions module/api_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
''' API Token Module '''
from typing import Any
from uuid import uuid4
from dataclasses import dataclass, asdict, field

from passlib.context import CryptContext

from models.api_token import APITokenDB

@dataclass
class APITokenSchema:
''' Schema of api_token collection '''
token: str = field(default_factory=lambda: uuid4().hex)
serial_no: str = field(default_factory=lambda: f'{uuid4().node:08x}')
label: str = ''

class APIToken:
''' Class for managing API tokens '''
@staticmethod
def create(label: str) -> APITokenSchema:
''' Create token '''
new_token = APITokenSchema(label=label)

hash_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
hashed_token = asdict(new_token)
hashed_token['token'] = hash_context.hash(hashed_token['token'])

APITokenDB().insert_one(hashed_token)

new_token.token = f'{new_token.serial_no}|{new_token.token}'

return new_token

@staticmethod
def get_list() -> list[dict[str, Any]]:
''' Get list of token '''
return list(APITokenDB().find({}, { 'label': 1, 'serial_no': 1, '_id': 0 }))

@staticmethod
def delete(tokens: list[str]) -> None:
''' Delete the given token serial_no '''
APITokenDB().delete_many({
'serial_no': { '$in': tokens }
})

@staticmethod
def verify(token: str) -> bool:
''' Check if the token exists and valid '''
schema, token = token.split(' ')
if schema.lower() != 'bearer':
return False

serial_no, token = token.split('|')
hash_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
hashed_token = APITokenDB().find_one({
'serial_no': serial_no
})

if hashed_token is None:
return False

return hash_context.verify(token, hashed_token['token'])
32 changes: 30 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pymongo = "^4.3.3"
requests = "^2.31.0"
uWSGI = "^2.0.21"
certifi = "*"
passlib = "^1.7.4"
types-passlib = "^1.7.7.20240327"


[tool.poetry.group.dev.dependencies]
Expand Down
3 changes: 3 additions & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
<span>{{session.u.name}}</span>
</a>
<div class="navbar-dropdown is-right">
<a class="navbar-item" href="/token">
<span class="icon"><i class="fas fa-key"></i></span> <span>API Token 設定</span>
</a>
<a class="navbar-item" href="/logout">
<span class="icon"><i class="fas fa-sign-out-alt"></i></span> <span>登出</span>
</a>
Expand Down
13 changes: 13 additions & 0 deletions templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ <h3>管理電子報訂閱者</h3>
</div>
</div>
</div>
<div class="columns">
<div class="column">
<div class="content">
<h3>電子報系統設定</h3>
</div>
<div class="buttons">
<a class="button is-outlined is-info is-light" href="/token">
<span class="icon"><i class="fas fa-key"></i></span>
<span>API Token 設定</span>
</a>
</div>
</div>
</div>
<div class="columns">
<div class="column">
<div class="content">
Expand Down
Loading