Skip to content

Commit

Permalink
Add GitHub authentication with fps-auth-fief (#324)
Browse files Browse the repository at this point in the history
* Add GitHub authentication with fps-auth-fief

* Allow auth callback to be set from config
  • Loading branch information
davidbrochart authored Jul 5, 2023
1 parent c0070bd commit e15a7e4
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 19 deletions.
28 changes: 15 additions & 13 deletions plugins/auth_fief/fps_auth_fief/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,31 @@
from .config import _AuthFiefConfig


class CustomFiefAuth(FiefAuth):
client: FiefAsync

async def get_unauthorized_response(self, request: Request, response: Response):
redirect_uri = str(request.url_for("auth_callback"))
auth_url = await self.client.auth_url(redirect_uri, scope=["openid"])
raise HTTPException(
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
headers={"Location": auth_url},
)


@dataclass
class Res:
fief: FiefAsync
session_cookie_name: str
auth: CustomFiefAuth
auth: FiefAuth
current_user: Any
update_user: Any
websocket_auth: Any


def get_backend(auth_fief_config: _AuthFiefConfig) -> Res:
class CustomFiefAuth(FiefAuth):
client: FiefAsync

async def get_unauthorized_response(self, request: Request, response: Response):
if auth_fief_config.callback_url:
redirect_uri = auth_fief_config.callback_url
else:
redirect_uri = str(request.url_for("auth_callback"))
auth_url = await self.client.auth_url(redirect_uri, scope=["openid", "offline_access"])
raise HTTPException(
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
headers={"Location": auth_url},
)

fief = FiefAsync(
auth_fief_config.base_url,
auth_fief_config.client_id,
Expand Down
10 changes: 7 additions & 3 deletions plugins/auth_fief/fps_auth_fief/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from jupyverse_api.auth import AuthConfig
from pydantic import Field


class _AuthFiefConfig(AuthConfig):
base_url: str # Base URL of Fief tenant
client_id: str # ID of Fief client
client_secret: str # Secret of Fief client
base_url: str = Field(description="Base URL of Fief tenant")
callback_url: str = Field(description="URL of the callback route", default="")
client_id: str = Field(description="ID of Fief client")
client_secret: str = Field(description="Secret of Fief client")
admin_api_key: str = Field(description="Admin API key", default="")
oauth_provider_id: str = Field(description="OAuth provider ID", default="")
86 changes: 83 additions & 3 deletions plugins/auth_fief/fps_auth_fief/routes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
from typing import Any, Callable, Dict, List, Optional, Tuple

import httpx
from fastapi import APIRouter, Depends, Query, Request, Response
from fastapi.responses import RedirectResponse
from fief_client import FiefAccessTokenInfo
Expand All @@ -25,9 +26,88 @@ def __init__(self) -> None:
router = APIRouter()

@router.get("/auth-callback", name="auth_callback")
async def auth_callback(request: Request, response: Response, code: str = Query(...)):
redirect_uri = str(request.url_for("auth_callback"))
tokens, _ = await backend.fief.auth_callback(code, redirect_uri)
async def auth_callback(
request: Request,
response: Response,
code: str = Query(...),
):
if auth_fief_config.callback_url:
redirect_uri = auth_fief_config.callback_url
else:
redirect_uri = str(request.url_for("auth_callback"))
tokens, user_info = await backend.fief.auth_callback(code, redirect_uri)

user_id = user_info["sub"]
async with httpx.AsyncClient() as client:
headers = {"Authorization": f"Bearer {auth_fief_config.admin_api_key}"}
r = await client.get(
f"{auth_fief_config.base_url}/admin/api/oauth-providers/{auth_fief_config.oauth_provider_id}/access-token/{user_id}",
headers=headers,
)

# FIXME: this is hard-coded for GitHub authentication
access_token = r.json()["access_token"]
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
}
r = await client.get("https://api.github.com/user", headers=headers)

d = r.json()
data = {
"username": d["login"],
"name": d["name"],
"display_name": d["name"],
"initials": "".join([word[0] for word in d["name"].split()]),
"avatar_url": d["avatar_url"],
}
await backend.fief.update_profile(tokens["access_token"], {"fields": data})

# set permissions
async with httpx.AsyncClient() as client:
headers = {
"Authorization": f"Bearer {auth_fief_config.admin_api_key}",
"accept": "application/json",
}
skip = 0
nb = 100
count = None
got = 0
while True:
params = {"limit": nb, "skip": skip}
r = await client.get(
f"{auth_fief_config.base_url}/admin/api/permissions/",
headers=headers,
params=params,
)
d = r.json()
if count is None:
count = d["count"]

permission_id = {
result["codename"]: result["id"] for result in d["results"]
}

headers = {"Authorization": f"Bearer {auth_fief_config.admin_api_key}"}
for permission, id in permission_id.items():
data = {"id": id}
await client.post(
f"{auth_fief_config.base_url}/admin/api/users/{user_id}/permissions",
headers=headers,
json=data,
)

got += len(d["results"])
if got < count:
skip += nb
else:
break

refresh_token = tokens["refresh_token"]
assert refresh_token is not None
tokens, user_info = await backend.fief.auth_refresh_token(refresh_token)

response = RedirectResponse(request.url_for("root"))
response.set_cookie(
Expand Down

0 comments on commit e15a7e4

Please sign in to comment.