Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
D
douban-api-proxy
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
captainwong
douban-api-proxy
Commits
2b55b9b2
Commit
2b55b9b2
authored
Dec 08, 2019
by
Michael Shavit
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use the login_user Cookie to authorize download requests instead of
passing the UserKey over url params.
parent
fffa2d5a
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
21 additions
and
30 deletions
+21
-30
kobo.py
cps/kobo.py
+2
-4
kobo_auth.py
cps/kobo_auth.py
+19
-26
No files found.
cps/kobo.py
View file @
2b55b9b2
...
@@ -218,10 +218,8 @@ def get_metadata__v1(book_uuid):
...
@@ -218,10 +218,8 @@ def get_metadata__v1(book_uuid):
def
get_download_url_for_book
(
book
):
def
get_download_url_for_book
(
book
):
return
"{url_base}/download/{book_id}/kepub?{auth_token_param}"
.
format
(
return
"{url_base}/download/{book_id}/kepub"
.
format
(
url_base
=
config
.
config_server_url
,
url_base
=
config
.
config_server_url
,
book_id
=
book
.
id
book_id
=
book
.
id
,
auth_token_param
=
kobo_auth
.
get_auth_url_param
(
request
),
)
)
...
...
cps/kobo_auth.py
View file @
2b55b9b2
...
@@ -19,17 +19,23 @@ issue for a few years now https://www.mobileread.com/forums/showpost.php?p=34768
...
@@ -19,17 +19,23 @@ issue for a few years now https://www.mobileread.com/forums/showpost.php?p=34768
(although this poster hypothesised that Kobo could blacklist a DeviceId, many endpoints
(although this poster hypothesised that Kobo could blacklist a DeviceId, many endpoints
will still grant access given the userkey.)
will still grant access given the userkey.)
Api authorization:
Official Kobo Store
Api authorization:
* For most of the endpoints we care about (sync, metadata, tags, etc), the userKey is
* For most of the endpoints we care about (sync, metadata, tags, etc), the userKey is
passed in the x-kobo-userkey header, and is sufficient to authorize the API call.
passed in the x-kobo-userkey header, and is sufficient to authorize the API call.
* Some endpoints (e.g: AnnotationService) instead make use of Bearer tokens. To get a
* Some endpoints (e.g: AnnotationService) instead make use of Bearer tokens pass through
BearerToken, the device makes a POST request to the v1/auth/device endpoint with the
an authorization header. To get a BearerToken, the device makes a POST request to the
secret UserKey and the device's DeviceId.
v1/auth/device endpoint with the secret UserKey and the device's DeviceId.
* The book download endpoint passes an auth token as a URL param instead of a header.
Our implementation:
Our implementation:
For now, we rely on the official Kobo store's UserKey for authentication. Because of the
For now, we rely on the official Kobo store's UserKey for authentication.
irrevocable power granted by the key, we only ever store and compare a hash of the key.
Once authenticated, we set the login cookie on the response that will be sent back for
To obtain their UserKey, a user can query the user table from the
the duration of the session to authorize subsequent API calls.
Ideally we'd only perform UserKey-based authentication for the v1/initialization or the
v1/device/auth call, however sessions don't always start with those calls.
Because of the irrevocable power granted by the key, we only ever store and compare a
hash of the key. To obtain their UserKey, a user can query the user table from the
.kobo/KoboReader.sqlite database found on their device.
.kobo/KoboReader.sqlite database found on their device.
This isn't exactly user friendly however.
This isn't exactly user friendly however.
...
@@ -38,13 +44,14 @@ Some possible alternatives that require more research:
...
@@ -38,13 +44,14 @@ Some possible alternatives that require more research:
provide a list of recent Kobo sync attempts in the calibre-web UI for users to
provide a list of recent Kobo sync attempts in the calibre-web UI for users to
authenticate sync attempts (e.g: 'this was me' button).
authenticate sync attempts (e.g: 'this was me' button).
* We may be able to craft a sign-in flow with a redirect back to the CalibreWeb
* We may be able to craft a sign-in flow with a redirect back to the CalibreWeb
server containing the KoboStore's UserKey.
server containing the KoboStore's UserKey
(if the same as the devices?)
.
* Can we create our own UserKey instead of relying on the real store's userkey?
* Can we create our own UserKey instead of relying on the real store's userkey?
(Maybe using something like location.href=kobo://UserAuthenticated?userId=...?)
(Maybe using something like location.href=kobo://UserAuthenticated?userId=...?)
"""
"""
from
functools
import
wraps
from
functools
import
wraps
from
flask
import
request
,
make_response
from
flask
import
request
,
make_response
from
flask_login
import
login_user
from
werkzeug.security
import
check_password_hash
from
werkzeug.security
import
check_password_hash
from
.
import
logger
,
ub
,
lm
from
.
import
logger
,
ub
,
lm
...
@@ -61,29 +68,15 @@ def disable_failed_auth_redirect_for_blueprint(bp):
...
@@ -61,29 +68,15 @@ def disable_failed_auth_redirect_for_blueprint(bp):
@
lm
.
request_loader
@
lm
.
request_loader
def
load_user_from_kobo_request
(
request
):
def
load_user_from_kobo_request
(
request
):
user_key
=
get_auth_token_from_request
(
request
)
user_key
=
request
.
headers
.
get
(
USER_KEY_HEADER
)
if
user_key
:
if
user_key
:
for
user
in
(
for
user
in
(
ub
.
session
.
query
(
ub
.
User
)
.
filter
(
ub
.
User
.
kobo_user_key_hash
!=
""
)
.
all
()
ub
.
session
.
query
(
ub
.
User
)
.
filter
(
ub
.
User
.
kobo_user_key_hash
!=
""
)
.
all
()
):
):
if
check_password_hash
(
str
(
user
.
kobo_user_key_hash
),
user_key
):
if
check_password_hash
(
str
(
user
.
kobo_user_key_hash
),
user_key
):
# The Kobo device won't preserve the cookie accross sessions, even if we
# were to set remember_me=true.
login_user
(
user
)
return
user
return
user
log
.
info
(
"Received Kobo request without a recognizable UserKey."
)
log
.
info
(
"Received Kobo request without a recognizable UserKey."
)
return
None
return
None
def
get_auth_token_from_request
(
request
):
user_key
=
request
.
headers
.
get
(
USER_KEY_HEADER
)
if
not
user_key
:
user_key
=
request
.
args
.
get
(
USER_KEY_URL_PARAM
)
return
user_key
def
get_auth_url_param
(
request
):
# Some of the API requests emitted by the Kobo device don't set any headers. To
# support those calls, authorization on those endpoints can only rely on URL params.
# Since the raw UserKey in already leaked in headers, it is probably not *that* much
# worse to also leak it over url params.
# Ideally however, we should be generating short-lived tokens that grant limited
# access instead..
return
USER_KEY_URL_PARAM
+
"="
+
get_auth_token_from_request
(
request
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment