feat: link local library
This commit is contained in:
+222
@@ -0,0 +1,222 @@
|
||||
from enum import Enum
|
||||
from typing import List, Literal, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, HttpUrl, conlist, constr, validator
|
||||
|
||||
# TODO: Schema of claims is not genrated
|
||||
|
||||
|
||||
class AcrValues(str, Enum):
|
||||
l1 = "https://www.spid.gov.it/SpidL1"
|
||||
l2 = "https://www.spid.gov.it/SpidL2"
|
||||
l3 = "https://www.spid.gov.it/SpidL3"
|
||||
|
||||
|
||||
class ScopeSpid(str, Enum):
|
||||
openid = "openid"
|
||||
offline_access = "offline_access"
|
||||
|
||||
|
||||
class ScopeCie(str, Enum):
|
||||
openid = "openid"
|
||||
offline_access = "offline_access"
|
||||
profile = "profile"
|
||||
email = "email"
|
||||
|
||||
|
||||
class ClaimsTypeEssential(BaseModel):
|
||||
essential: Optional[bool]
|
||||
|
||||
|
||||
class ClaimsTypeStringValue(BaseModel):
|
||||
value: Optional[str]
|
||||
|
||||
|
||||
class ClaimsTypeStringValues(BaseModel):
|
||||
values: Optional[conlist(str, max_items=2, min_items=2)]
|
||||
|
||||
|
||||
class ClaimsType(str, Enum):
|
||||
essential = ClaimsTypeEssential
|
||||
value = ClaimsTypeStringValue
|
||||
values = ClaimsTypeStringValues
|
||||
|
||||
|
||||
class UserInfoSpid(BaseModel):
|
||||
name: Optional[dict] = Field(
|
||||
alias="given_name", default=None
|
||||
)
|
||||
family_name: Optional[dict] = Field(
|
||||
alias="family_name", default=None
|
||||
)
|
||||
place_of_birth: Optional[dict] = Field(
|
||||
alias="place_of_birth", default=None
|
||||
)
|
||||
date_of_birth: Optional[dict] = Field(
|
||||
alias="birthdate", default=None
|
||||
)
|
||||
gender: Optional[dict] = Field(
|
||||
alias="gender", default=None
|
||||
)
|
||||
company_name: Optional[dict] = Field(
|
||||
alias="https://attributes.eid.gov.it/company_name", default=None
|
||||
)
|
||||
registered_office: Optional[dict] = Field(
|
||||
alias="https://attributes.eid.gov.it/registered_office", default=None
|
||||
)
|
||||
fiscal_number: Optional[dict] = Field(
|
||||
alias="https://attributes.eid.gov.it/fiscal_number", default=None
|
||||
)
|
||||
iva_code: Optional[dict] = Field(
|
||||
alias="https://attributes.eid.gov.it/vat_number", default=None
|
||||
)
|
||||
id_card: Optional[dict] = Field(
|
||||
alias="https://attributes.eid.gov.it/document_details", default=None
|
||||
)
|
||||
mobile_phone: Optional[dict] = Field(
|
||||
alias="https://attributes.eid.gov.it/phone_number", default=None
|
||||
)
|
||||
email: Optional[dict] = Field(
|
||||
alias="email", default=None
|
||||
)
|
||||
address: Optional[dict] = Field(
|
||||
alias="address", default=None
|
||||
)
|
||||
expiration_date: Optional[dict] = Field(
|
||||
alias="https://attributes.eid.gov.it/eid_exp_date", default=None
|
||||
)
|
||||
digital_address: Optional[dict] = Field(
|
||||
alias="https://attributes.eid.gov.it/e_delivery_service", default=None
|
||||
)
|
||||
|
||||
|
||||
class UserInfoCie(BaseModel):
|
||||
given_name: Optional[dict]
|
||||
family_name: Optional[dict]
|
||||
email: Optional[dict]
|
||||
email_verified: Optional[dict]
|
||||
gender: Optional[dict]
|
||||
birthdate: Optional[dict]
|
||||
phone_number: Optional[dict]
|
||||
phone_number_verified: Optional[dict]
|
||||
address: Optional[dict]
|
||||
place_of_birth: Optional[dict]
|
||||
document_details: Optional[dict]
|
||||
e_delivery_service: Optional[dict]
|
||||
fiscal_number: Optional[dict]
|
||||
physical_phone_number: Optional[dict]
|
||||
|
||||
|
||||
class IdToken(UserInfoCie):
|
||||
pass
|
||||
|
||||
|
||||
TYPES = {
|
||||
"essential": ClaimsTypeEssential,
|
||||
"value": ClaimsTypeStringValue,
|
||||
"values": ClaimsTypeStringValues,
|
||||
}
|
||||
|
||||
CLAIMS_SPID = {"userinfo": UserInfoSpid}
|
||||
|
||||
CLAIMS_CIE = {"userinfo": UserInfoCie, "id_token": IdToken}
|
||||
|
||||
|
||||
class AuthenticationRequest(BaseModel):
|
||||
client_id: HttpUrl
|
||||
response_type: Literal["code"]
|
||||
scope: List[str]
|
||||
code_challenge: str
|
||||
code_challenge_method: Literal["S256"]
|
||||
nonce: constr(min_length=32)
|
||||
redirect_uri: HttpUrl
|
||||
claims: Optional[dict]
|
||||
state: constr(min_length=32)
|
||||
# TODO: to be improved
|
||||
ui_locales: Optional[List[str]]
|
||||
|
||||
# sub claim MUST not be used to prevent that this jwt
|
||||
# could be reused as a private_key_jwt
|
||||
# sub: HttpUrl
|
||||
|
||||
iss: HttpUrl
|
||||
iat: int
|
||||
exp: Optional[int]
|
||||
jti: Optional[str]
|
||||
aud: str | List[HttpUrl]
|
||||
acr_values: Optional[List[AcrValues]]
|
||||
prompt: Optional[Literal["consent", "consent login"]]
|
||||
|
||||
@validator("claims")
|
||||
def validate_claims(cls, claims):
|
||||
for k_claim, v_claim in claims.items():
|
||||
cl = cls.get_claims()
|
||||
claims_items = cl.get(k_claim, None)
|
||||
if not claims_items:
|
||||
continue
|
||||
claims_items(**v_claim)
|
||||
for k_item, v_item in v_claim.items():
|
||||
if v_item is not None:
|
||||
for k_type, v_type in TYPES.items():
|
||||
v_type(**v_item)
|
||||
return claims
|
||||
|
||||
@validator("scope")
|
||||
def validate_scope(cls, scope):
|
||||
if "openid" not in scope:
|
||||
raise ValueError("'scope' attribute must contain 'openid'")
|
||||
|
||||
|
||||
class AuthenticationRequestSpid(AuthenticationRequest):
|
||||
scope: List[ScopeSpid]
|
||||
|
||||
def get_claims() -> dict:
|
||||
return CLAIMS_SPID
|
||||
|
||||
def example(): # pragma: no cover
|
||||
return AuthenticationRequestSpid( # nosec B106
|
||||
client_id= "https://rp.cie.it/callback1/",
|
||||
response_type= "code",
|
||||
scope= ["openid", "offline_access"],
|
||||
code_challenge= "codeChallenge",
|
||||
code_challenge_method= "S256",
|
||||
nonce= "12345678123456781234567812345678inpiu",
|
||||
prompt= "consent",
|
||||
redirect_uri= "https://rp.cie.it/callback1/",
|
||||
acr_values= ["https://www.spid.gov.it/SpidL2", "https://www.spid.gov.it/SpidL1"],
|
||||
claims= {},
|
||||
state= "fyZiOL9Lf2CeKuNT2JzxiLRDink0uPcd",
|
||||
ui_locales= ["codice1", "codice2", "codice3"],
|
||||
sub= "https://rp.cie.it/",
|
||||
iss= "https://op.spid.agid.gov.it/",
|
||||
aud= ["https://rp.spid.agid.gov.it/auth"],
|
||||
iat= 1648591200,
|
||||
exp= 1648592200,
|
||||
jti= "a72d5df0-2415-4c7c-a44f-3988b354040b",
|
||||
)
|
||||
|
||||
|
||||
class AuthenticationRequestCie(AuthenticationRequest):
|
||||
scope: List[ScopeCie]
|
||||
|
||||
def get_claims() -> dict:
|
||||
return CLAIMS_CIE
|
||||
|
||||
|
||||
class AuthenticationRequestDoc(BaseModel):
|
||||
client_id: HttpUrl
|
||||
response_type: Literal["code"]
|
||||
scope: List[str]
|
||||
code_challenge: str
|
||||
code_challenge_method: Literal["S256"]
|
||||
request: constr(regex=r"^[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+") # noqa: F722
|
||||
|
||||
def example(): # pragma: no cover
|
||||
return AuthenticationRequestDoc( # nosec B106
|
||||
client_id= "https://rp.cie.it/callback1/",
|
||||
response_type= "code",
|
||||
scope= ["openid", "offline_access"],
|
||||
code_challenge= "codeChallenge",
|
||||
code_challenge_method= "S256",
|
||||
request= "eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ew0KICJpc3MiOiAiczZCaGRSa3F0MyIsDQogImF1ZCI6ICJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsDQogInJlc3BvbnNlX3R5cGUiOiAiY29kZSBpZF90b2tlbiIsDQogImNsaWVudF9pZCI6ICJzNkJoZFJrcXQzIiwNCiAicmVkaXJlY3RfdXJpIjogImh0dHBzOi8vY2xpZW50LmV4YW1wbGUub3JnL2NiIiwNCiAic2NvcGUiOiAib3BlbmlkIiwNCiAic3RhdGUiOiAiYWYwaWZqc2xka2oiLA0KICJub25jZSI6ICJuLTBTNl9XekEyTWoiLA0KICJtYXhfYWdlIjogODY0MDAsDQogImNsYWltcyI6IA0KICB7DQogICAidXNlcmluZm8iOiANCiAgICB7DQogICAgICJnaXZlbl9uYW1lIjogeyJlc3NlbnRpYWwiOiB0cnVlfSwNCiAgICAgI.qq",
|
||||
)
|
||||
@@ -0,0 +1,44 @@
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, HttpUrl, constr
|
||||
|
||||
|
||||
class AuthenticationResponse(BaseModel):
|
||||
# TODO: is 1*VSCHAR
|
||||
code: str
|
||||
state: constr(min_length=32)
|
||||
|
||||
def example(): # pragma: no cover
|
||||
return AuthenticationResponse( # nosec B106
|
||||
code= "usDwMnEzJPpG5oaV8x3j&",
|
||||
state= "fyZiOL9Lf2CeKuNT2JzxiLRDink0uPcd",
|
||||
)
|
||||
|
||||
|
||||
class AuthenticationResponseCie(AuthenticationResponse):
|
||||
iss: HttpUrl
|
||||
|
||||
def example(): # pragma: no cover
|
||||
return AuthenticationResponse( # nosec B106
|
||||
code= "usDwMnEzJPpG5oaV8x3j&",
|
||||
state= "fyZiOL9Lf2CeKuNT2JzxiLRDink0uPcd",
|
||||
iss= "https://idserver.servizicie.interno.gov.it",
|
||||
)
|
||||
|
||||
|
||||
class AuthenticationErrorResponse(BaseModel):
|
||||
error: Literal[
|
||||
"invalid_request",
|
||||
"unauthorized_client",
|
||||
"access_denied",
|
||||
"unsupported_response_type",
|
||||
"invalid_scope",
|
||||
"server_error",
|
||||
"temporarily_unavailable",
|
||||
]
|
||||
error_description: str
|
||||
state: constr(min_length=32)
|
||||
|
||||
|
||||
class AuthenticationErrorResponseCie(AuthenticationErrorResponse):
|
||||
iss: HttpUrl
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
from spid_cie_oidc.entity.utils import iat_now
|
||||
|
||||
from pydantic import BaseModel, AnyHttpUrl, validator
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class ClientAssertion(BaseModel):
|
||||
iss: AnyHttpUrl
|
||||
sub: AnyHttpUrl
|
||||
iat: int
|
||||
exp: int
|
||||
jti: Optional[str]
|
||||
aud: str | List[AnyHttpUrl]
|
||||
|
||||
@validator("sub")
|
||||
def iss_and_sub_must_match(cls, sub, values):
|
||||
if values['iss'] != sub:
|
||||
raise ValueError(
|
||||
'Client Assertion: iss and sub must have the same value'
|
||||
)
|
||||
return sub
|
||||
|
||||
@validator("exp")
|
||||
def not_expired(cls, exp, values):
|
||||
_now = iat_now()
|
||||
if not (values['iat'] <= _now < exp):
|
||||
raise ValueError(
|
||||
'Client Assertion: exp must be greater than '
|
||||
'iat and less than the current time.'
|
||||
f'{values["iat"]} <= {_now} < {exp}'
|
||||
)
|
||||
return exp
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
from typing import Literal
|
||||
from pydantic import BaseModel, HttpUrl, constr
|
||||
|
||||
|
||||
class IntrospectionRequest(BaseModel):
|
||||
client_assertion: constr(regex=r"^[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+") # noqa: F722
|
||||
client_assertion_type: Literal[
|
||||
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
||||
]
|
||||
client_id: HttpUrl
|
||||
token: constr(regex=r"^[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+") # noqa: F722
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IntrospectionResponse(BaseModel):
|
||||
active: bool
|
||||
# scope: Optional[List[str]]
|
||||
# sub: Optional[str]
|
||||
# exp: Optional[int]
|
||||
# client_id: HttpUrl
|
||||
# iss: Optional[HttpUrl]
|
||||
# TODO: migliorare: array di url con almeno uno
|
||||
# aud: Optional[List[HttpUrl]]
|
||||
|
||||
|
||||
class IntrospectionErrorResponse(BaseModel):
|
||||
error_description: str
|
||||
|
||||
|
||||
class IntrospectionErrorResponseSpid(IntrospectionErrorResponse):
|
||||
error: Literal[
|
||||
"invalid_client", "invalid_request", "server_error", "temporarily_unavailable"
|
||||
]
|
||||
|
||||
|
||||
class IntrospectionErrorResponseCie(IntrospectionErrorResponse):
|
||||
error: Literal[
|
||||
"invalid_client",
|
||||
"invalid_request",
|
||||
"invalid_token",
|
||||
"server_error",
|
||||
"temporarily_unavailable",
|
||||
]
|
||||
@@ -0,0 +1,67 @@
|
||||
import datetime
|
||||
from typing import List
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from pydantic import BaseModel, HttpUrl, validator
|
||||
from spid_cie_oidc.entity.utils import datetime_from_timestamp
|
||||
|
||||
|
||||
try:
|
||||
import spid_cie_oidc.entity.settings as entity_settings
|
||||
|
||||
NOW = timezone.localtime()
|
||||
MAX_ACCEPTED_TIMEDIFF = getattr(
|
||||
settings, "MAX_ACCEPTED_TIMEDIFF", entity_settings.MAX_ACCEPTED_TIMEDIFF
|
||||
)
|
||||
except ImportError: # pragma: no cover
|
||||
NOW = datetime.datetime.now()
|
||||
MAX_ACCEPTED_TIMEDIFF = 5
|
||||
|
||||
|
||||
class JwtStructure(BaseModel):
|
||||
iss: HttpUrl
|
||||
sub: HttpUrl
|
||||
iat: int
|
||||
exp: int
|
||||
jti: str
|
||||
# TODO: migliorare: array di url con almeno uno
|
||||
aud: List[HttpUrl]
|
||||
|
||||
@validator("exp")
|
||||
def validate_exp(cls, exp):
|
||||
now = int(NOW.timestamp())
|
||||
if exp <= now:
|
||||
raise ValueError("exp MUST be in the future")
|
||||
|
||||
@validator("iat")
|
||||
def validate_iat(cls, iat):
|
||||
dt_iat = datetime_from_timestamp(iat)
|
||||
delta = datetime.timedelta(minutes=MAX_ACCEPTED_TIMEDIFF)
|
||||
if abs((NOW - dt_iat).total_seconds()) > delta.total_seconds():
|
||||
raise ValueError(f"iat MUST be in the last {MAX_ACCEPTED_TIMEDIFF} minutes")
|
||||
|
||||
|
||||
class IdTokenJwt(JwtStructure):
|
||||
# TODO: implementare
|
||||
pass
|
||||
|
||||
|
||||
class AccessTokenJwt(JwtStructure):
|
||||
# TODO: implementare
|
||||
pass
|
||||
|
||||
|
||||
class RefreshTokenJwt(JwtStructure):
|
||||
# TODO: implementare
|
||||
pass
|
||||
|
||||
|
||||
class UserInfoResponseJwtHeader(BaseModel):
|
||||
# TODO: implementare
|
||||
pass
|
||||
|
||||
|
||||
class UserInfoResponseJwtPayload(JwtStructure):
|
||||
# TODO: implementare
|
||||
pass
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, HttpUrl, constr
|
||||
|
||||
|
||||
class RevocationRequest(BaseModel):
|
||||
client_assertion: constr(regex=r"^[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+") # noqa: F722
|
||||
client_assertion_type: Literal[
|
||||
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
||||
]
|
||||
client_id: HttpUrl
|
||||
token: constr(regex=r"^[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+") # noqa: F722
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class RevocationErrorResponse(BaseModel):
|
||||
error: Literal[
|
||||
"invalid_request",
|
||||
"invalid_client",
|
||||
"unauthorized_client",
|
||||
"invalid_scope",
|
||||
"unsupported_token_type",
|
||||
]
|
||||
error_description: str
|
||||
@@ -0,0 +1,43 @@
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, HttpUrl, constr
|
||||
|
||||
|
||||
class TokenRequest(BaseModel):
|
||||
client_id: HttpUrl
|
||||
client_assertion: constr(regex=r"^[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+") # noqa: F722
|
||||
client_assertion_type: Literal[
|
||||
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
||||
]
|
||||
|
||||
|
||||
class TokenAuthnCodeRequest(TokenRequest):
|
||||
# TODO: is 1*VSCHAR
|
||||
code: str
|
||||
# TODO: is 43*128unreserved, where unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
code_verifier: str
|
||||
grant_type: Literal["authorization_code"]
|
||||
|
||||
def example():
|
||||
return TokenAuthnCodeRequest(
|
||||
client_id = "http://example.com",
|
||||
client_assertion = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjJIbm9GUzNZbkM5dGppQ2FpdmhXTFZVSjNBeHdHR3pfOTh1UkZhcU1FRXMifQ.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwMDAvb2lkYy9ycC8iLCJzdWIiOiJodHRwOi8vMTI3LjAuMC4xOjgwMDAvb2lkYy9ycC8iLCJhdWQiOlsiaHR0cDovLzEyNy4wLjAuMTo4MDAwL29pZGMvb3AvdG9rZW4vIl0sImlhdCI6MTY0ODQ5NTc1NywiZXhwIjoxNjQ4NDk3NzM3LCJqdGkiOiI0OWE2OGQyMS03NTE1LTRmNDMtODZhZC1hMDNhMWNmNDFmNTgifQ.EaWmmuDhoa8jt_go4zrY5AhTGnljIS1zNwIF-f9eBidAiWPYaKwmXRsRoXrAeybLcJ5E8-7TO1S8jZiCxcElQPdRRvuP9ZsgNhfEqDhZtabkwGBFt4gpQZnFsgGDMAi-v4sTM55VsnIJIMrHPNIckZfL-YhD-FSCtDsqrCkCnucXR58Kfp_SEx3hJvrU-2vcOeNXJBTqs3pHl9jws9GJIf6bZd7vQqTMolCC4zBzUp7yT3OXqMt9uCUhLZzLtpTRW0_-u-KW4h44eHOdEf6ePqiFhjflF9_T6cM-OBqzaK0eVvg6RcasvF3IyxdG-ME5crapAibeePqg_Hy__Q5idQ",
|
||||
client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
code = "bb03ad1f2e838335c1050ae280a64f4e195dbeb17ba8aa2dc6fd99e1c5181fc735be6af10b634fbf0f04f533843e199d0261bf570585ca292cb4fb8857480af5",
|
||||
code_verifier = "nd6gJEXzUZApV46u4DmVUfZgBYc2SJsZGkaBftof4gcI95ByALg",
|
||||
grant_type = "authorization_code"
|
||||
)
|
||||
|
||||
|
||||
class TokenRefreshRequest(TokenRequest):
|
||||
grant_type: Literal["refresh_token"]
|
||||
refresh_token: str
|
||||
|
||||
def example():
|
||||
return TokenRefreshRequest( # nosec B106
|
||||
client_id = "http://example.com",
|
||||
client_assertion = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjJIbm9GUzNZbkM5dGppQ2FpdmhXTFZVSjNBeHdHR3pfOTh1UkZhcU1FRXMifQ.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwMDAvb2lkYy9ycC8iLCJzdWIiOiJodHRwOi8vMTI3LjAuMC4xOjgwMDAvb2lkYy9ycC8iLCJhdWQiOlsiaHR0cDovLzEyNy4wLjAuMTo4MDAwL29pZGMvb3AvdG9rZW4vIl0sImlhdCI6MTY0ODQ5NTc1NywiZXhwIjoxNjQ4NDk3NzM3LCJqdGkiOiI0OWE2OGQyMS03NTE1LTRmNDMtODZhZC1hMDNhMWNmNDFmNTgifQ.EaWmmuDhoa8jt_go4zrY5AhTGnljIS1zNwIF-f9eBidAiWPYaKwmXRsRoXrAeybLcJ5E8-7TO1S8jZiCxcElQPdRRvuP9ZsgNhfEqDhZtabkwGBFt4gpQZnFsgGDMAi-v4sTM55VsnIJIMrHPNIckZfL-YhD-FSCtDsqrCkCnucXR58Kfp_SEx3hJvrU-2vcOeNXJBTqs3pHl9jws9GJIf6bZd7vQqTMolCC4zBzUp7yT3OXqMt9uCUhLZzLtpTRW0_-u-KW4h44eHOdEf6ePqiFhjflF9_T6cM-OBqzaK0eVvg6RcasvF3IyxdG-ME5crapAibeePqg_Hy__Q5idQ",
|
||||
client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
refresh_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImRCNjdnTDdjazNURmlJQWY3TjZfN1NIdnFrME1EWU1FUWNvR0dsa1VBQXcifQ.eyJpc3MiOiJodHRwOi8vb3AtdGVzdC9vaWRjL29wLyIsInN1YiI6Imh0dHA6Ly9ycC10ZXN0Lml0L29pZGMvcnAvIiwiZXhwIjoxNjQ4NDk3OTU2LCJpYXQiOjE2NDg0OTU5NzYsImF1ZCI6WyJodHRwOi8vcnAtdGVzdC5pdC9vaWRjL3JwLyJdLCJjbGllbnRfaWQiOiJodHRwOi8vcnAtdGVzdC5pdC9vaWRjL3JwLyIsInNjb3BlIjoib3BlbmlkIn0.yDF6BsFpT1h2JdSxyE3hE8chcF0Lq0OtGzRoZkBbN7otHTvAsTcupT-J6JTHnO817Bp1kllvLkJ5X4p3NeElOL-TbzF1xtX2azpiaxZD4xEGOYsMGkRnLeJP9LWGYbJXEBlAjHMpYW-DhZz3cE3iJvmaehi6sIRsXMPszsDIrt2cYmwFg9KrEc2Ty_qWKdIhzgxq82FEgMk2e2ehez3MTH7KAni2hRmtl32w6uf7_cfW5GlLfDjjVNm2Y9NkmrFeKmX0YUOQWXTts1MhlGfnw9ASgBstgfB5tWWmxF6Sg7ojAnPt_eKH75gcMLhUxj4iif8Aj0QNPzXuJ7VlBH9VAA",
|
||||
grant_type = "refresh_token"
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, constr
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
access_token: constr(regex=r"^[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+") # noqa: F722
|
||||
token_type: Literal["Bearer"]
|
||||
expires_in: int
|
||||
id_token: constr(regex=r"^[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+") # noqa: F722
|
||||
|
||||
|
||||
class TokenRefreshResponse(TokenResponse):
|
||||
refresh_token: constr(regex=r"^[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+\.[a-zA-Z\_\-0-9]+") # noqa: F722
|
||||
|
||||
|
||||
class TokenErrorResponse(BaseModel):
|
||||
error: Literal["invalid_request", "unauthorized_client"]
|
||||
error_description: str
|
||||
Reference in New Issue
Block a user