Add self resouce provider
This commit is contained in:
parent
dc181bd3a8
commit
af49242192
7 changed files with 77 additions and 29 deletions
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ class OIDCProvider(BaseModel):
|
|||
)
|
||||
public_key: str | None = None
|
||||
signature_alg: str = "RS256"
|
||||
resource_provider_scopes: list[str] = []
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue