Merge pull request #212 from mawoka-myblock/custom-openid-oauth

This commit is contained in:
Mawoka
2023-02-17 15:22:41 +01:00
committed by GitHub
8 changed files with 210 additions and 42 deletions
+10 -1
View File
@@ -6,13 +6,20 @@ from functools import lru_cache
from redis import asyncio as redis_lib from redis import asyncio as redis_lib
import redis as redis_base_lib import redis as redis_base_lib
from pydantic import BaseSettings, RedisDsn, PostgresDsn from pydantic import BaseSettings, RedisDsn, PostgresDsn, BaseModel
import meilisearch as MeiliSearch import meilisearch as MeiliSearch
from typing import Optional from typing import Optional
from classquiz.storage import Storage from classquiz.storage import Storage
class CustomOpenIDProvider(BaseModel):
scopes: str = "openid email profile"
server_metadata_url: str
client_id: str
client_secret: str
class Settings(BaseSettings): class Settings(BaseSettings):
""" """
Settings class for the shop app. Settings class for the shop app.
@@ -39,6 +46,7 @@ class Settings(BaseSettings):
google_client_secret: Optional[str] google_client_secret: Optional[str]
github_client_id: Optional[str] github_client_id: Optional[str]
github_client_secret: Optional[str] github_client_secret: Optional[str]
custom_openid_provider: CustomOpenIDProvider | None = None
telemetry_enabled: bool = True telemetry_enabled: bool = True
# storage_backend # storage_backend
@@ -53,6 +61,7 @@ class Settings(BaseSettings):
class Config: class Config:
env_file = ".env" env_file = ".env"
env_file_encoding = "utf-8" env_file_encoding = "utf-8"
env_nested_delimiter = "__"
@lru_cache() @lru_cache()
+1
View File
@@ -18,6 +18,7 @@ class UserAuthTypes(Enum):
LOCAL = "LOCAL" LOCAL = "LOCAL"
GOOGLE = "GOOGLE" GOOGLE = "GOOGLE"
GITHUB = "GITHUB" GITHUB = "GITHUB"
CUSTOM = "CUSTOM"
class User(ormar.Model): class User(ormar.Model):
+2 -1
View File
@@ -9,7 +9,7 @@ from jose import jws, jwt, JWTError, JWSError
from classquiz.auth import ACCESS_TOKEN_EXPIRE_MINUTES, create_access_token from classquiz.auth import ACCESS_TOKEN_EXPIRE_MINUTES, create_access_token
from classquiz.db.models import UserSession from classquiz.db.models import UserSession
from classquiz.oauth import google, github from classquiz.oauth import google, github, custom
from classquiz.config import settings from classquiz.config import settings
settings = settings() settings = settings()
@@ -17,6 +17,7 @@ settings = settings()
router = APIRouter() router = APIRouter()
router.include_router(google.router, prefix="/google") router.include_router(google.router, prefix="/google")
router.include_router(github.router, prefix="/github") router.include_router(github.router, prefix="/github")
router.include_router(custom.router, prefix="/custom")
async def rememberme_middleware(request: Request, call_next): async def rememberme_middleware(request: Request, call_next):
+120
View File
@@ -0,0 +1,120 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
import uuid
import asyncpg
from fastapi import APIRouter, Request, HTTPException, Response
from classquiz.config import settings
from classquiz.db.models import User, UserAuthTypes
from classquiz.auth import check_token
from classquiz.helpers.avatar import gzipped_user_avatar
from classquiz.oauth.authenticate_user import log_user_in, rememberme_check
from classquiz.oauth.init_oauth import init_oauth
from pydantic import BaseModel, ValidationError
settings = settings()
router = APIRouter()
class Userinfo(BaseModel):
exp: int
iat: int
iss: str
aud: str
sub: uuid.UUID
nonce: str
email: str
email_verified: bool
preferred_username: str
name: str
class OpenIDResponse(BaseModel):
access_token: str
expires_in: int
token_type: str
scope: str
refresh_token: str
id_token: str
expires_at: int
userinfo: Userinfo
@router.get("/login")
async def openid_login(req: Request):
if settings.custom_openid_provider.client_id is None or settings.custom_openid_provider.client_secret is None:
raise HTTPException(status_code=501, detail="Custom-OAuth-Login isn't available on this server")
oauth = init_oauth()
return await oauth.custom.authorize_redirect(req, f"{settings.root_address}/api/v1/users/oauth/custom/auth")
@router.get("/auth")
async def auth(request: Request, response: Response):
if settings.custom_openid_provider.client_id is None or settings.custom_openid_provider.client_secret is None:
raise HTTPException(status_code=501, detail="Custom-OAuth-Login isn't available on this server")
access_token = request.cookies.get("access_token")
rememberme_token = request.cookies.get("rememberme_token")
if access_token is not None:
try:
data = await check_token(access_token)
if data is not None:
return
except HTTPException:
pass
if rememberme_token is not None:
return await rememberme_check(rememberme_token=rememberme_token, response=response)
oauth = init_oauth()
user_data = await oauth.custom.authorize_access_token(request)
try:
user_data = OpenIDResponse(**user_data).userinfo
except (TypeError, ValidationError):
raise HTTPException(status_code=401, detail="Something went wrong.")
user_in_db = await User.objects.get_or_none(email=user_data.email)
if user_in_db is None:
# REGISTER USER
try:
await User.objects.create(
id=uuid.uuid4(),
email=user_data.email,
username=user_data.preferred_username,
verified=user_data.email_verified,
auth_type=UserAuthTypes.CUSTOM,
google_uid=user_data.sub.hex,
avatar=gzipped_user_avatar(),
)
except Exception as e:
if type(e) == asyncpg.exceptions.UniqueViolationError:
error = True
counter = 1
while error:
try:
await User.objects.create(
id=uuid.uuid4(),
email=user_data.email,
username=f"{user_data.preferred_username}{counter}",
verified=user_data.email_verified,
auth_type=UserAuthTypes.CUSTOM,
google_uid=user_data.sub.hex,
avatar=gzipped_user_avatar(),
)
error = False
except asyncpg.exceptions.UniqueViolationError:
counter += 1
error = True
else:
raise HTTPException(status_code=500, detail=str(e))
user = await User.objects.get_or_none(
email=user_data.email, google_uid=user_data.sub.hex, auth_type=UserAuthTypes.CUSTOM, verified=True
)
print(user_data)
await log_user_in(user=user, request=request, response=response)
response.headers.append("Location", "/account/login")
response.status_code = 302
return response
+7
View File
@@ -31,4 +31,11 @@ def init_oauth() -> OAuth:
client_id=settings.github_client_id, client_id=settings.github_client_id,
client_secret=settings.github_client_secret, client_secret=settings.github_client_secret,
) )
oauth.register(
name="custom",
client_kwargs={"scope": settings.custom_openid_provider.scopes},
server_metadata_url=settings.custom_openid_provider.server_metadata_url,
client_id=settings.custom_openid_provider.client_id,
client_secret=settings.custom_openid_provider.client_secret,
)
return oauth return oauth
+1
View File
@@ -7,3 +7,4 @@
export const google_auth_enabled = import.meta.env.VITE_GOOGLE_AUTH_ENABLED === 'true'; export const google_auth_enabled = import.meta.env.VITE_GOOGLE_AUTH_ENABLED === 'true';
export const github_auth_enabled = import.meta.env.VITE_GITHUB_AUTH_ENABLED === 'true'; export const github_auth_enabled = import.meta.env.VITE_GITHUB_AUTH_ENABLED === 'true';
export const captcha_enabled = import.meta.env.VITE_CAPTCHA_ENABLED === 'true'; export const captcha_enabled = import.meta.env.VITE_CAPTCHA_ENABLED === 'true';
export const custom_oauth_name = import.meta.env.VITE_CUSTOM_OAUTH_NAME;
@@ -0,0 +1,67 @@
<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-->
<script lang="ts">
import { github_auth_enabled, google_auth_enabled, custom_oauth_name } from '$lib/config';
</script>
{#if google_auth_enabled}
<div class="flex items-center justify-center pt-4">
<a
href="/api/v1/users/oauth/google/login"
class="inline-flex w-fit p-1 rounded-lg border-gray-500 border border-2 hover:bg-[#4285F4] transition"
>Google Login
<svg
class="h-6 w-6 ml-4 dark:fill-gray-300"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><title> Google</title>
<path
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
/>
</svg>
</a>
</div>
{/if}
{#if github_auth_enabled}
<div class="flex items-center w-full justify-center pt-4">
<a
href="/api/v1/users/oauth/github/login"
class="inline-flex w-fit p-1 rounded-lg border-gray-500 border border-2 hover:bg-[#181717] transition hover:text-white group"
>GitHub Login
<svg
class="h-6 w-6 ml-4 dark:fill-gray-300 group-hover:fill-white transition"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><title> GitHub</title>
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
/>
</svg>
</a>
</div>
{/if}
{#if custom_oauth_name}
<div class="flex items-center w-full justify-center pt-4">
<a
href="/api/v1/users/oauth/custom/login"
class="inline-flex w-fit p-1 rounded-lg border-gray-500 border border-2 hover:bg-[#F78C40] transition hover:text-white group"
>OpenID Login
<svg
class="h-6 w-6 ml-4 dark:fill-gray-300 group-hover:fill-white transition"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><title>OpenID</title>
<path
d="M14.54.889l-3.63 1.773v18.17c-4.15-.52-7.27-2.78-7.27-5.5 0-2.58 2.8-4.75 6.63-5.41v-2.31C4.42 8.322 0 11.502 0 15.332c0 3.96 4.74 7.24 10.91 7.78l3.63-1.71V.888m.64 6.724v2.31c1.43.25 2.71.7 3.76 1.31l-1.97 1.11 7.03 1.53-.5-5.21-1.87 1.06c-1.74-1.06-3.96-1.81-6.45-2.11z"
/>
</svg>
</a>
</div>
{/if}
@@ -4,8 +4,8 @@
- file, You can obtain one at https://mozilla.org/MPL/2.0/. - file, You can obtain one at https://mozilla.org/MPL/2.0/.
--> -->
<script lang="ts"> <script lang="ts">
import { github_auth_enabled, google_auth_enabled } from '$lib/config';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import OAuthBlock from './oauth_block.svelte';
export let session_data = {}; export let session_data = {};
export let step; export let step;
@@ -93,45 +93,7 @@
{/if} {/if}
</button> </button>
</div> </div>
{#if google_auth_enabled} <OAuthBlock />
<div class="flex items-center justify-center pt-4">
<a
href="/api/v1/users/oauth/google/login"
class="inline-flex w-fit p-1 rounded-lg border-gray-500 border border-2 hover:bg-[#4285F4] transition"
>Google Login
<svg
class="h-6 w-6 ml-4 dark:fill-gray-300"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><title> Google</title>
<path
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
/>
</svg>
</a>
</div>
{/if}
{#if github_auth_enabled}
<div class="flex items-center w-full justify-center pt-4">
<a
href="/api/v1/users/oauth/github/login"
class="inline-flex w-fit p-1 rounded-lg border-gray-500 border border-2 hover:bg-[#181717] transition hover:text-white group"
>GitHub Login
<svg
class="h-6 w-6 ml-4 dark:fill-gray-300 group-hover:fill-white transition"
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><title> GitHub</title>
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
/>
</svg>
</a>
</div>
{/if}
</div> </div>
</form> </form>
</div> </div>