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 GitHub authentication #110

Merged
merged 6 commits into from
Jun 4, 2024
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
3 changes: 2 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ DISCORD_CLIENT_SECRET=

GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_CALLBACK=http://localhost:8000/github
ABANDON_AUTH_GITHUB_CALLBACK='http://localhost:8000/ui/github-callback'
ABANDON_AUTH_GITHUB_REDIRECT="https://github.com/login/oauth/authorize?client_id=$GITHUB_CLIENT_ID&redirect_uri=$ABANDON_AUTH_GITHUB_CALLBACK&scope=user:email"

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
Expand Down
8 changes: 3 additions & 5 deletions abandonauth/routers/github.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import httpx
from fastapi import APIRouter
from prisma.models import User
from starlette.responses import RedirectResponse

from abandonauth.dependencies.auth.jwt import generate_short_lived_jwt
from abandonauth.models import JwtDto
Expand All @@ -13,12 +12,11 @@
)


@router.get("", response_model=JwtDto)
async def login_with_github(code: str, state: str) -> RedirectResponse:
async def login_with_github(code: str, application_id: str) -> JwtDto:
"""Log a user in using GitHubs's OAuth2 as validation."""
data = {
"client_id": settings.GITHUB_CLIENT_ID,
"redirect_uri": settings.GITHUB_CALLBACK,
"redirect_uri": settings.ABANDON_AUTH_GITHUB_CALLBACK,
"client_secret": settings.GITHUB_CLIENT_SECRET.get_secret_value(),
"code": code,
}
Expand Down Expand Up @@ -58,4 +56,4 @@ async def login_with_github(code: str, state: str) -> RedirectResponse:
},
})

return RedirectResponse(f"{state}?authentication={generate_short_lived_jwt(user.id, 'fake_application_id')}")
return JwtDto(token=generate_short_lived_jwt(user.id, application_id))
39 changes: 39 additions & 0 deletions abandonauth/routers/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from abandonauth.dependencies.services import build_abandon_auth_redirect_url, user_info_from_me_response
from abandonauth.models import DeveloperApplicationWithCallbackUriDto, DiscordLoginDto
from abandonauth.routers.discord import login_with_discord
from abandonauth.routers.github import login_with_github
from abandonauth.settings import settings

router = APIRouter(prefix="/ui")
Expand Down Expand Up @@ -346,12 +347,14 @@ async def oauth_login(
)

discord_login_url = f"{settings.ABANDON_AUTH_DISCORD_REDIRECT}&state={dev_app.id},{callback_uri}"
github_login_url = f"{settings.ABANDON_AUTH_GITHUB_REDIRECT}&state={dev_app.id},{callback_uri}"

return jinja_templates.TemplateResponse(
"login.html",
{
"request": request,
"discord_redirect": discord_login_url,
"github_redirect": github_login_url,
"errors": errors,
},
)
Expand Down Expand Up @@ -392,3 +395,39 @@ async def discord_callback(request: Request) -> RedirectResponse:
return RedirectResponse(f"{redirect_url}?code={exchange_token}")

return RedirectResponse(redirect_url)


@router.get("/github-callback")
async def github_callback(request: Request) -> RedirectResponse:
"""GitHub callback endpoint for authenticating with GitHub OAuth with AbandonAuth UI."""
code = request.query_params.get("code")

# Application ID and callback URI are verified before being added to the discord callback URI
# The state param can be altered via user input on previous steps and must be verified here
if request_state := request.query_params.get("state"):
app_id, redirect_url = request_state.split(",")

dev_app = await DeveloperApplication.prisma().find_unique(
where={"id": app_id},
include={"callback_uris": True},
)

# This check is very important. application ID and callback URI must be validated
# The state in the github login URL cannot be trusted
if not dev_app or not dev_app.callback_uris or redirect_url not in [x.uri for x in dev_app.callback_uris]:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN,
detail="Invalid application ID or callback_uri",
)
else:
raise HTTPException(
status_code=HTTP_400_BAD_REQUEST,
detail="application_id and callback_uri are required in query param 'state'",
)

if code:
exchange_token = (await login_with_github(code, app_id)).token

return RedirectResponse(f"{redirect_url}?code={exchange_token}")

return RedirectResponse(redirect_url)
7 changes: 4 additions & 3 deletions abandonauth/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ class Settings(BaseSettings):
JWT_EXPIRES_IN_SECONDS_LONG_LIVED: int
JWT_EXPIRES_IN_SECONDS_SHORT_LIVED: int

ABANDON_AUTH_DISCORD_REDIRECT: str
ABANDON_AUTH_DISCORD_CALLBACK: str
ABANDON_AUTH_DEVELOPER_APP_ID: str
ABANDON_AUTH_DEVELOPER_APP_TOKEN: str

ABANDON_AUTH_DISCORD_REDIRECT: str
ABANDON_AUTH_DISCORD_CALLBACK: str
DISCORD_CLIENT_ID: str
DISCORD_CLIENT_SECRET: pydantic.SecretStr

ABANDON_AUTH_GITHUB_CALLBACK: str
ABANDON_AUTH_GITHUB_REDIRECT: str
GITHUB_CLIENT_ID: str
GITHUB_CLIENT_SECRET: pydantic.SecretStr
GITHUB_CALLBACK: str

GOOGLE_CLIENT_ID: str
GOOGLE_CLIENT_SECRET: pydantic.SecretStr
Expand Down
2 changes: 1 addition & 1 deletion abandonauth/templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ <h2 id="subtitle">Application wants you to login using AbandonAuth!</h2>
Login with Discord
</button>

<button id="login-github">
<button id="login-github" onclick="location.href='{{ github_redirect }}'" type="button">
<svg viewBox="0 0 39 39" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.2101 30.9016C13.2101 31.0561 13.0339 31.1797 12.8117 31.1797C12.5589 31.2028 12.3827 31.0792 12.3827 30.9016C12.3827 30.7471 12.5589 30.6235 12.781 30.6235C13.0109 30.6003 13.2101 30.7239 13.2101 30.9016ZM10.8274 30.554C10.7738 30.7085 10.927 30.8861 11.1569 30.9325C11.356 31.0097 11.5859 30.9325 11.6319 30.778C11.6778 30.6235 11.5323 30.4458 11.3024 30.3763C11.1032 30.3222 10.881 30.3995 10.8274 30.554ZM14.2137 30.4226C13.9915 30.4767 13.8383 30.6235 13.8613 30.8012C13.8843 30.9557 14.0835 31.0561 14.3133 31.002C14.5355 30.9479 14.6887 30.8012 14.6657 30.6467C14.6427 30.4999 14.4359 30.3995 14.2137 30.4226ZM19.2548 0.821167C8.62863 0.821167 0.5 8.95539 0.5 19.6697C0.5 28.2365 5.84758 35.5674 13.4859 38.1474C14.4665 38.3251 14.8113 37.7149 14.8113 37.2127C14.8113 36.7338 14.7883 34.0919 14.7883 32.4697C14.7883 32.4697 9.4254 33.6284 8.29919 30.1677C8.29919 30.1677 7.42581 27.9198 6.16935 27.3404C6.16935 27.3404 4.41492 26.1276 6.29194 26.1508C6.29194 26.1508 8.1996 26.3053 9.24919 28.1438C10.927 31.1256 13.7387 30.2681 14.8343 29.7583C15.0105 28.5223 15.5085 27.6649 16.0601 27.155C11.7774 26.6761 7.45645 26.0504 7.45645 18.6191C7.45645 16.4948 8.03871 15.4288 9.26452 14.0692C9.06532 13.5671 8.41411 11.4969 9.46371 8.82407C11.0649 8.32196 14.75 10.9098 14.75 10.9098C16.2823 10.4772 17.9294 10.2532 19.5613 10.2532C21.1931 10.2532 22.8403 10.4772 24.3726 10.9098C24.3726 10.9098 28.0577 8.31423 29.6589 8.82407C30.7085 11.5046 30.0573 13.5671 29.8581 14.0692C31.0839 15.4365 31.8347 16.5025 31.8347 18.6191C31.8347 26.0736 27.3222 26.6684 23.0395 27.155C23.7444 27.7653 24.3419 28.924 24.3419 30.7394C24.3419 33.3426 24.319 36.5639 24.319 37.1973C24.319 37.6994 24.6714 38.3097 25.6444 38.132C33.3056 35.5674 38.5 28.2365 38.5 19.6697C38.5 8.95539 29.881 0.821167 19.2548 0.821167ZM7.94677 27.464C7.84718 27.5413 7.87016 27.719 8.0004 27.8657C8.12298 27.9893 8.29919 28.0434 8.39879 27.943C8.49839 27.8657 8.4754 27.6881 8.34516 27.5413C8.22258 27.4177 8.04637 27.3636 7.94677 27.464ZM7.11936 26.8383C7.06573 26.9387 7.14234 27.0623 7.29556 27.1396C7.41815 27.2168 7.57137 27.1937 7.625 27.0855C7.67863 26.9851 7.60202 26.8615 7.44879 26.7843C7.29556 26.7379 7.17298 26.7611 7.11936 26.8383ZM9.60161 29.5884C9.47903 29.6888 9.525 29.9205 9.70121 30.0673C9.87742 30.245 10.0996 30.2681 10.1992 30.1445C10.2988 30.0441 10.2528 29.8124 10.0996 29.6656C9.93105 29.4879 9.70121 29.4648 9.60161 29.5884ZM8.72823 28.4528C8.60565 28.5301 8.60565 28.7309 8.72823 28.9086C8.85081 29.0862 9.05766 29.1635 9.15726 29.0862C9.27984 28.9858 9.27984 28.785 9.15726 28.6073C9.05 28.4296 8.85081 28.3524 8.72823 28.4528Z" fill="#F7F7F7"/>
</svg>
Expand Down
22 changes: 22 additions & 0 deletions docs/GITHUB-OAUTH2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# GitHub OAuth2 Setup

### 1. Goto [GitHub Developer Apps](https://github.com/settings/developers)

### 2. Create a New Application
![GitHub Create Application](./imgs/github-create-application.png)

### 3. Add details
![GitHub Create Application Details](./imgs/github-create-application-details.png)

### 4. Get Client ID and Secrets
![GitHub Client ID And Secrets](./imgs/github-get-clientid-and-clientsecret.png)

### 5. Fill `.env` file

```dotenv
# GitHub Application Details for OAuth2
GITHUB_CLIENT_ID=<Client ID in step 4>
GITHUB_CLIENT_SECRET=<Client Secret in step 4>
ABANDON_AUTH_GITHUB_CALLBACK='http://localhost:8000/ui/github-callback'
ABANDON_AUTH_GITHUB_REDIRECT="https://github.com/login/oauth/authorize?client_id=$GITHUB_CLIENT_ID&redirect_uri=$ABANDON_AUTH_GITHUB_CALLBACK&scope=user:email"
```
Binary file added docs/imgs/github-create-application-details.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/imgs/github-create-application.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.