Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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 @@ -38,6 +41,8 @@
app.register_blueprint(VIEW_SUBSCRIBE)
app.register_blueprint(VIEW_SUBSCRIBER)
app.register_blueprint(VIEW_TRELLO)
app.register_blueprint(VIEW_VOLUNTEER)
app.register_blueprint(VIEW_TOKEN)
Copy link
Member

Choose a reason for hiding this comment

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

Line 39 - Line 45 這裡排序一下。

Copy link
Member Author

Choose a reason for hiding this comment

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

已修正


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)])
3 changes: 3 additions & 0 deletions models/index.py
Copy link
Member

Choose a reason for hiding this comment

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

這個檔案上方 import 好像沒有排序,APITokenDB().index() 應該可以移上去 Line 7。

Copy link
Member Author

Choose a reason for hiding this comment

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

已修正

Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
''' index '''
from models.subscriberdb import (SubscriberDB, SubscriberLoginTokenDB,
SubscriberReadDB)
from models.api_token import APITokenDB

if __name__ == '__main__':
SubscriberDB().index()
SubscriberLoginTokenDB().index()
SubscriberReadDB().index()

APITokenDB().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