Add self resouce provider
Some checks failed
/ test (push) Successful in 6s
/ build (push) Failing after 14s

This commit is contained in:
phil 2025-02-04 02:27:32 +01:00
parent dc181bd3a8
commit af49242192
7 changed files with 77 additions and 29 deletions

View file

@ -4,13 +4,12 @@ import logging
from fastapi import HTTPException, Request, Depends, status
from fastapi.security import OAuth2PasswordBearer
from authlib.oauth2.rfc6749 import OAuth2Token
from authlib.integrations.starlette_client import OAuth, OAuthError, StarletteOAuth2App
from jwt import ExpiredSignatureError, InvalidKeyError
from httpx import AsyncClient
# from authlib.oauth1.auth import OAuthToken
# from authlib.oauth2.auth import OAuth2Token
from authlib.oauth2.auth import OAuth2Token
from .models import User
from .database import TokenNotInDb, db, UserNotInDB
@ -21,7 +20,6 @@ logger = logging.getLogger(__name__)
oidc_providers_settings: dict[str, OIDCProvider] = dict(
[(provider.id, provider) for provider in settings.oidc.providers]
)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@ -37,9 +35,19 @@ async def fetch_token(name, request):
# return token.to_token()
async def update_token(*args, **kwargs):
logger.warn("TODO: update_token")
...
async def update_token(name, token, refresh_token=None, access_token=None):
breakpoint()
if refresh_token:
item = OAuth2Token.find(name=name, refresh_token=refresh_token)
elif access_token:
item = OAuth2Token.find(name=name, access_token=access_token)
else:
return
# update old token
item.access_token = token["access_token"]
item.refresh_token = token.get("refresh_token")
item.expires_at = token["expires_at"]
item.save()
authlib_oauth = OAuth(cache=None, fetch_token=fetch_token, update_token=update_token)
@ -52,7 +60,10 @@ def init_providers():
name=id,
server_metadata_url=provider.openid_configuration,
client_kwargs={
"scope": "openid email offline_access profile",
"scope": " ".join(
["openid", "email", "offline_access", "profile"]
+ provider.resource_provider_scopes
),
},
client_id=provider.client_id,
client_secret=provider.client_secret,

View file

@ -102,6 +102,7 @@ async def home(
context={
"settings": settings.model_dump(),
"user": user,
"access_token_scope": user.access_token_parsed()["scope"] if user else None,
"now": now,
"oidc_provider": oidc_provider,
"oidc_provider_settings": oidc_provider_settings,

View file

@ -2,6 +2,7 @@ from datetime import datetime
import logging
from httpx import AsyncClient
from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
from .models import User
@ -22,15 +23,20 @@ async def get_resource(resource_id: str, user: User) -> dict:
# but this has to be refined for production
required_scope = f"get:{resource_id}"
# Check if the required scope is in the scopes allowed in userinfo
if user.has_scope(required_scope):
await process(user, resource_id, resp)
else:
## For the showcase, giving a explanation.
## Alternatively, raise HTTP_401_UNAUTHORIZED
resp["sorry"] = (
f"No scope {required_scope} in the access token "
+ "but it is required for accessing this resource."
)
try:
if user.has_scope(required_scope):
await process(user, resource_id, resp)
else:
## For the showcase, giving a explanation.
## Alternatively, raise HTTP_401_UNAUTHORIZED
resp["sorry"] = (
f"No scope {required_scope} in the access token "
+ "but it is required for accessing this resource."
)
except ExpiredSignatureError:
resp["sorry"] = "The token's signature has expired"
except InvalidTokenError:
resp["sorry"] = "The token is invalid"
return resp

View file

@ -45,6 +45,7 @@ class OIDCProvider(BaseModel):
)
public_key: str | None = None
signature_alg: str = "RS256"
resource_provider_scopes: list[str] = []
@computed_field
@property

View file

@ -170,3 +170,23 @@ hr {
border-radius: 8px;
}
.resource {
padding: 0.5em;
display: flex;
gap: 0.5em;
flex-direction: column;
width: fit-content;
align-items: center;
margin: 5px auto;
box-shadow: 0px 0px 10px #90c3eeA0;
background-color: #90c3eeA0;
border-radius: 8px;
}
.resources {
display: flex;
}
.key {
font-weight: bold;
}

View file

@ -36,5 +36,21 @@ async function get_resource(id, token, authProvider) {
err => msg.value = err
)
*/
console.log(await resp.json())
const resource = await resp.json()
const rootElem = document.getElementById('resource')
rootElem.innerHTML = ""
Object.entries(resource).forEach(
([k, v]) => {
let r = document.createElement('div')
let kElem = document.createElement('div')
kElem.innerText = k
kElem.className = "key"
let vElem = document.createElement('div')
vElem.innerText = v
vElem.className = "value"
r.appendChild(kElem)
r.appendChild(vElem)
rootElem.appendChild(r)
}
)
}

View file

@ -42,10 +42,10 @@
{% endfor %}
</div>
{% endif %}
{% if user.access_token.scope %}
{% if access_token_scope %}
<div>
<span>Scopes</span>:
{% for scope in user.access_token.scope.split(' ') %}
{% for scope in access_token_scope.split(' ') %}
<span class="scope">{{ scope }}</span>
{% endfor %}
</div>
@ -61,6 +61,7 @@
</div>
{% endif %}
<hr>
{% if user %}
<p class="center">
Fetch resources from the resource server with your authentication token:
</p>
@ -68,17 +69,9 @@
<button onclick="get_resource('time', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">Time</button>
<button onclick="get_resource('bs', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">BS</button>
</div>
<div class="resources">
<div v-if="Object.entries(resource).length > 0" class="resource">
<div v-for="(value, key) in resource">
<div class="key">{{ key }}</div>
<div v-if="key == 'sorry' || key == 'error'" class="error">{{ value }}</div>
<div v-else class="value">{{ value }}</div>
</div>
</div>
</div>
<div v-if="msg" class="msg resource">{{ msg }}</div>
<div id="resource" class="resource"></div>
<hr>
{% endif %}
<div class="content">
<p>
These links should get different response codes depending on the authorization: