Skip to content

Commit

Permalink
make oauth login form actually verify creds
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidBuchanan314 committed Jan 17, 2025
1 parent 8946477 commit a0e1e8d
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 22 deletions.
50 changes: 36 additions & 14 deletions src/millipds/auth_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ async def oauth_authorization_server(request: web.Request):
async def oauth_authorize(request: web.Request):
# TODO: extract request_uri
return web.Response(
text=html_templates.authn_page(), # this includes a login form that POSTs to /oauth/authorize (i.e. same endpoint)
text=html_templates.authn_page(
identifier_hint="todo.invalid"
), # this includes a login form that POSTs to /oauth/authorize (i.e. same endpoint)
content_type="text/html",
headers=WEBUI_HEADERS,
)
Expand All @@ -135,12 +137,31 @@ async def oauth_authorize(request: web.Request):
# authorize the client application to access certain scopes
@routes.post("/oauth/authorize")
async def oauth_authorize_handle_login(request: web.Request):
# TODO: actually handle login
return web.Response(
text=html_templates.authz_page(),
content_type="text/html",
headers=WEBUI_HEADERS,
)
# TODO: CSRF tokens?
form = await request.post()
logging.info(form)

db = get_db(request)
form_identifier = form.get("identifier", "")
form_password = form.get("password", "")

try:
did, handle = db.verify_account_login(form_identifier, form_password)
return web.Response(
text=html_templates.authz_page(handle=handle),
content_type="text/html",
headers=WEBUI_HEADERS,
)
except:
# TODO: error
return web.Response(
text=html_templates.authn_page(
identifier_hint=form_identifier,
error_msg="incorrect identifier or password",
),
content_type="text/html",
headers=WEBUI_HEADERS,
)


DPOP_NONCE = "placeholder_nonce_value" # this needs to get rotated! (does it matter that it's global?)
Expand All @@ -158,9 +179,9 @@ async def dpop_handler(request: web.Request):
)
jwk_data = unverified["header"]["jwk"]
jwk = jwt.PyJWK.from_dict(jwk_data)
decoded: dict = jwt.decode(
dpop, key=jwk
) # actual signature verification happens here

# actual signature verification happens here:
decoded: dict = jwt.decode(dpop, key=jwk)

logger.info(decoded)
logger.info(request.url)
Expand Down Expand Up @@ -210,14 +231,15 @@ async def dpop_handler(request: web.Request):
@routes.post("/oauth/par")
@dpop_protected
async def oauth_pushed_authorization_request(request: web.Request):
data = (
await request.json()
) # TODO: doesn't rfc9126 say it's posted as form data?
# NOTE: rfc9126 says this is posted as form data, but this is atproto-flavoured oauth
data = await request.json()
logging.info(data)

assert data["client_id"] == request["dpop_iss"] # idk if this is required

# we need to store the request somewhere, and associate it with the URI we return
# TODO: request client metadata???

# TODO: we need to store the request somewhere, and associate it with the URI we return

return web.json_response(
{
Expand Down
30 changes: 22 additions & 8 deletions src/millipds/html_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
to make this source look nice
"""

from html import escape

html = str

AUTH_PANEL_HEAD: html = """\
Expand Down Expand Up @@ -64,6 +66,7 @@
font-weight: bold;
box-shadow: 2px 2px #000;
margin-bottom: 0;
text-shadow: 1px 1px rgba(0,0,0,0.4);
/*border: 0.1px solid #fff;*/
}
Expand All @@ -83,7 +86,8 @@
}
.error {
background-color: #ff0048;
color: #000;
background-color: #f0ec57;
padding: 1em;
margin-top: 1.5em;
}
Expand Down Expand Up @@ -142,19 +146,29 @@
"""


def authn_page() -> str:
authn_body: html = """\
def authn_page(identifier_hint, error_msg=None) -> str:
authn_body: html = f"""\
<h3>put yer creds in the box.</h3>
<form action="" method="POST">
<label>handle: <input type="text" name="handle" value="todo.invalid" placeholder="bob.example.org"></label>
<label>password: <input type="password" name="password" placeholder="password"></label>
<label>identifier: <input required type="text" name="identifier" value="{escape(identifier_hint)}" placeholder="bob.example.org"></label>
<label>password: <input required type="password" name="password" placeholder="password"></label>
<input type="submit" value="sign in">
</form>"""
if error_msg:
error_html: html = f"""\
<div class="error">
<div class="gg-danger"></div>
<h3>oops</h3>
<p>{escape(error_msg)}</p>
</div>
"""
authn_body += error_html
return AUTH_PANEL_HEAD + authn_body + AUTH_PANEL_TAIL


def authz_page() -> str:
authz_body: html = """\
def authz_page(handle) -> str:
authz_body: html = f"""\
<p>Hello, <code>{escape(handle)}</code></p>
<h3>application <code>http://localhost/foobar.json</code> wants permission to:</h3>
<ul>
<li>eat the last donut</li>
Expand All @@ -174,7 +188,7 @@ def error_page(msg: str) -> str:
<div class="error">
<div class="gg-danger"></div>
<h3>oops</h3>
<p>{msg}</p>
<p>{escape(msg)}</p>
</div>
"""

Expand Down

0 comments on commit a0e1e8d

Please sign in to comment.