Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add first draft of oauth/indieauth #7

Merged
merged 1 commit into from
Jan 25, 2021
Merged
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
1 change: 1 addition & 0 deletions core/addons/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ async def post(self, core: ApplicationCore, request: web.Request) -> web.Respons

_LOGGER.warning(f"request: {original_filename}")

# TODO: get storage directory for user
path = os.path.join(f"./data/users/{user}/", original_filename)
if os.path.exists(path):
# TODO: handle filename already exists
Expand Down
104 changes: 104 additions & 0 deletions core/authentication/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Initialization of authentication."""
import json
import logging
import secrets

import aiohttp_jinja2
from aiohttp import web
from authlib.oauth2 import AuthorizationServer, ClientAuthentication, OAuth2Request

from core.webserver.request import ComplexEncoder
from core.webserver.status import HTTP_OK
from core.webserver.type import APPLICATION_JSON

_LOGGER = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)


class Auth(AuthorizationServer):
def __init__(self, application):
self.app = application

self.app.router.add_post(
path="/login", handler=self.login_handler2, name="auth:login2"
)
self.app.router.add_get(
path="/oauth/authorize",
handler=self.authorization_handler,
name="auth:authorize",
)
self.app.router.add_post(
path="/oauth/authorize",
handler=self.authorization_handler2,
name="auth:authorize2",
)
self.app.router.add_post(
path="/oauth/access_token",
handler=self.access_handler,
name="auth:access_token",
)

async def login_handler2(self, request):
_LOGGER.warning("POST /login")
response_type = "code"
client_id = "ABCDEF123"
redirect_uri = request.host
scope = "user public_photos"
state = secrets.token_hex(16)

raise web.HTTPFound(
location=f"http://127.0.0.1:9090/oauth/authorize?response_type={response_type}&client_id={client_id}&redirect_uri={redirect_uri}&scope={scope}&state={state}"
)

@aiohttp_jinja2.template("tmpl.jinja2")
async def authorization_handler(self, request):
"""Endpoint where the user get its authorization."""
_LOGGER.warning("GET /oauth/access_token")
_LOGGER.warning(f"request: {request.host}")

response_type = request.query.get("response_type")
client_id = request.query.get("client_id")
redirect_uri = request.query.get("redirect_uri")
scope = request.query.get("scope")
state = request.query.get("state")

_LOGGER.warning(f"a: {response_type}")
_LOGGER.warning(f"b: {client_id}")
_LOGGER.warning(f"c: {redirect_uri}")
_LOGGER.warning(f"d: {scope}")
_LOGGER.warning(f"e: {state}")

_LOGGER.warning(f"request: {request.query_string}")
# _LOGGER.warning(f"request: {request.__dict__}")

return {"redirect_uri": redirect_uri}

async def authorization_handler2(self, request):
_LOGGER.warning("POST /oauth/access_token")
data = await request.post()
return web.Response()

async def access_handler(self, request):
"""Endpoint to request an access token."""
_LOGGER.warning("GET /oauth/access_token")

success = True
if success:
msg = {
"access_token": "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
"scope": "create",
}
response = web.Response(
body=json.dumps(msg, cls=ComplexEncoder, allow_nan=False).encode(
"UTF-8"
),
content_type=APPLICATION_JSON,
status=HTTP_OK,
)
response.enable_compression()
return response
else:
return web.Response()
14 changes: 13 additions & 1 deletion core/webserver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
from ipaddress import ip_address
from typing import TYPE_CHECKING

import aiohttp_jinja2
import jinja2
from aiohttp import hdrs, web
from aiohttp.web_exceptions import HTTPForbidden, HTTPUnauthorized
from aiohttp.web_middlewares import middleware

from .. import const
from ..authentication import Auth
from .request import KEY_AUTHENTICATED, KEY_USER_ID, RequestView # noqa: F401

if TYPE_CHECKING:
Expand All @@ -29,6 +32,14 @@ def __init__(self, core: "ApplicationCore"):
self.app = web.Application(middlewares=[], client_max_size=MAX_CLIENT_SIZE)
self.runner = web.AppRunner(self.app)

# init jinja2 template engine
aiohttp_jinja2.setup(
self.app, loader=jinja2.FileSystemLoader("core/webserver/templates")
)

# init auth
self.auth = Auth(self.app)

self.app.middlewares.append(self.ban_middleware)
self.app.middlewares.append(self.auth_middleware)
self.app.middlewares.append(self.headers_middleware)
Expand Down Expand Up @@ -160,5 +171,6 @@ async def auth_middleware(self, request: web.Request, handler):
async def headers_middleware(self, request: web.Request, handler):
"""Add a server header to all responses."""
response = await handler(request)
response.headers["Server"] = f"Photos.network/{const.CORE_VERSION}"
if response is not None:
response.headers["Server"] = f"Photos.network/{const.CORE_VERSION}"
return response
19 changes: 19 additions & 0 deletions core/webserver/templates/tmpl.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<body style="background: #888888">
Do you wish to authorize the app <b>Frontend</b> to access your Photos.network?<br />
<br />
This application will be able to:<br />
<ul>
<li>Access all photos you own</li>
<li>Edit tags and meta data of your photos</li>
<li>Upload new photos to your storage</li>
</ul>
<br />
<br /><a href="{{ redirect_uri }}" type="submit">Sign In</a>
<br /><a href="#" type="submit">Cancel</a>
<br />
This application will not be able to:
<ul>
<li>See your password</li>
</ul>
<br />
You can revoke access to any application at any time from your Photos.network settings.
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
aiohttp>=3.7.0
aiohttp_cors==0.7.0
aiohttp-jinja2>=1.4.2
jinja2>=2.11.2
authlib>=0.15.3
astral==1.10.1
async_timeout==3.0.1
attrs==19.3.0
Expand All @@ -8,7 +11,6 @@ certifi>=2020.6.20
ciso8601==2.1.3
httpx==0.16.1
importlib-metadata==1.6.0;python_version<'3.8'
jinja2>=2.11.2
PyJWT==1.7.1
cryptography==3.2
pip>=8.0.3
Expand Down