Пример #1
0
async def generate_refresh_token(email: str, client_app: ClientApp) -> str:
    if not client_app.get_refresh_key(
    ) or not client_app.refresh_token_expire_hours:
        raise TokenCreationError("Refresh is not enabled")
    uid = str(uuid.uuid4())
    payload = {
        "iss": f"{config.ISSUER}/app/{client_app.app_id}",
        "sub": email,
        "uid": uid,
    }
    token = jwt.generate_jwt(
        payload,
        client_app.get_refresh_key(),
        "ES256",
        datetime.timedelta(hours=client_app.refresh_token_expire_hours),
    )
    token_hash = PWD_CONTEXT.hash(token)
    expires = datetime.datetime.now() + datetime.timedelta(
        hours=client_app.refresh_token_expire_hours)
    await RefreshToken(
        app_id=client_app.app_id,
        email=email,
        hash=token_hash,
        expires=expires,
        uid=uid,
    ).insert()
    return token
Пример #2
0
async def user1_app_no_refresh(user1, faker, monkeypatch):
    app = ClientApp(
        name=faker.company(),
        app_id=str(uuid.uuid4()),
        refresh_token_expire_hours=None,
        redirect_url="https://example.com/magic",
        failure_redirect_url="https://example.com/failed",
        owner=user1.email,
        quota=500,
        low_quota_threshold=10,
        unlimited=False,
    )
    key = jwk.JWK.generate(kty="EC", size=2048)
    app.set_key(key)
    await app.insert()
    return app
Пример #3
0
def generate(email: str, client_app: ClientApp) -> str:
    payload = {"iss": f"{config.ISSUER}/app/{client_app.app_id}", "sub": email}
    return jwt.generate_jwt(
        payload,
        client_app.get_key(),
        "ES256",
        datetime.timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES),
    )
Пример #4
0
async def create_client_app(
    app_name: str,
    owner: str,
    redirect_url: str,
    failure_redirect_url: str,
    refresh: bool = False,
    app_id: Optional[str] = None,
    refresh_token_expire_hours: int = 24,
    low_quota_threshold: int = 10,
) -> ClientApp:
    key = jwk.JWK.generate(kty="EC", size=2048)
    app_id = app_id or str(uuid.uuid4())
    app = ClientApp(
        name=app_name,
        app_id=app_id,
        owner=owner,
        key=None,
        refresh_key=None,
        refresh_token_expire_hours=None,
        redirect_url=redirect_url,
        low_quota_threshold=low_quota_threshold,
        failure_redirect_url=failure_redirect_url,
    )
    if refresh:
        app.set_refresh_key(jwk.JWK.generate(kty="EC", size=4096))
        app.refresh_token_expire_hours = refresh_token_expire_hours

    app.set_key(key)
    await app.insert()

    return app
Пример #5
0
async def verify_refresh_token(token: str, client_app: ClientApp) -> str:
    _, claims = _check_token(token, client_app.get_refresh_key(),
                             client_app.app_id)
    found_rt = await _find_refresh_token(claims, client_app)
    if found_rt.expires <= datetime.datetime.now():
        await found_rt.delete()
        raise TokenVerificationError("Expired Token. Please log in again.")
    if PWD_CONTEXT.verify(token, found_rt.hash):
        return generate(claims["sub"], client_app)
    raise TokenVerificationError("Could not find matching refresh token")
Пример #6
0
async def confirm_otp(
        confirm_code: ConfirmCode,
        client_app: ClientApp = Depends(check_client_app),
):
    """Confirm authentication by one time code"""
    if not security_otp.verify(confirm_code.email, confirm_code.code,
                               client_app.app_id):
        raise HTTPException(status_code=401, detail="Invalid Code.")
    id_token = security_token.generate(confirm_code.email, client_app)
    refresh_token = None
    if client_app.get_refresh_key():
        refresh_token = await security_token.generate_refresh_token(
            confirm_code.email, client_app)
    return IssueToken(idToken=id_token, refreshToken=refresh_token)
Пример #7
0
async def confirm_magic(
    secret: str = Query(...),
    id_: str = Query(..., alias="id"),
    client_app: ClientApp = Depends(check_client_app),
):
    """This endpoint confirms magic links. Do not use directly."""
    if email := security_magic.verify(id_, secret, client_app.app_id):
        id_token = security_token.generate(email, client_app)
        redirect_url = f"{client_app.redirect_url}?idToken={quote_plus(id_token)}"
        if client_app.get_refresh_key():
            refresh_token = await security_token.generate_refresh_token(
                email, client_app
            )
            redirect_url = f"{redirect_url}&refreshToken={quote_plus(refresh_token)}"
        return RedirectResponse(redirect_url)
Пример #8
0
 def _create(
     app_id=None,
     refresh=False,
     refresh_expire=None,
     failure_redirect_url=None,
     owner=None,
     quota=500,
     low_quota_threshold=10,
     low_quota_last_notified=None,
     unlimited=False,
     app_name=None,
 ):
     if not app_id:
         app_id = str(uuid.uuid4())
     if not app_name:
         app_name = faker.company()
     key = jwk.JWK.generate(kty="EC", size=2048)
     mocker.patch("mongox.Model.delete")
     mocker.patch("mongox.Model.save")
     _app = ClientApp(
         name=app_name,
         app_id=app_id,
         refresh_key=None,
         refresh_token_expire_hours=None,
         key=None,
         redirect_url="http://localhost",
         failure_redirect_url=failure_redirect_url,
         owner=owner,
         quota=quota,
         low_quota_threshold=low_quota_threshold,
         unlimited=unlimited,
     )
     if low_quota_last_notified:
         _app.low_quota_last_notified = low_quota_last_notified
     _app.set_key(key)
     if refresh:
         _app.set_refresh_key(jwk.JWK.generate(kty="EC", size=4096))
         _app.refresh_token_expire_hours = refresh_expire or 24
     return _app
Пример #9
0
async def test_generate_refresh_token(
    fake_email,
    fake_refresh_client_app: ClientApp,
    monkeypatch,
    pwd_context,
):
    fake_uuid = uuid.uuid4()
    monkeypatch.setattr(uuid, "uuid4", lambda: fake_uuid)
    refresh_token = await security_token.generate_refresh_token(
        fake_email, fake_refresh_client_app)
    assert refresh_token is not None

    headers, claims = jwt.verify_jwt(refresh_token,
                                     fake_refresh_client_app.get_refresh_key(),
                                     allowed_algs=["ES256"])

    assert headers["alg"] == "ES256"
    assert claims["sub"] == fake_email
    assert claims[
        "iss"] == f"{config.ISSUER}/app/{fake_refresh_client_app.app_id}"
    assert claims["uid"] == str(fake_uuid)

    # mock_insert.assert_called()
    # getting the seconds just right is annoying, so it's easiest just to check that
    # it's within about 10 minutes of the correct time.
    should_expire_lower_bound = (datetime.datetime.now() + datetime.timedelta(
        hours=fake_refresh_client_app.refresh_token_expire_hours) -
                                 datetime.timedelta(minutes=5))
    should_expire_upper_bound = (datetime.datetime.now() + datetime.timedelta(
        hours=fake_refresh_client_app.refresh_token_expire_hours) +
                                 datetime.timedelta(minutes=5))
    # print(dir(mock_insert))
    generated_rt: RefreshToken = (await RefreshToken.query(
        RefreshToken.email == claims["sub"]
    ).query(RefreshToken.app_id == fake_refresh_client_app.app_id
            ).query(RefreshToken.uid == claims["uid"]).get())

    assert generated_rt.expires >= should_expire_lower_bound
    assert generated_rt.expires <= should_expire_upper_bound
    assert pwd_context.verify(refresh_token, generated_rt.hash)
Пример #10
0
def createapp(
    app_name: str,
    url: str,
    refresh: bool,
    refresh_token_expire_hours: Optional[int],
    app_id: Optional[str],
):
    key = jwk.JWK.generate(kty="EC", size=2048)
    app_id = app_id or str(uuid.uuid4())
    app = ClientApp(
        name=app_name,
        app_id=app_id,
        key=None,
        refresh_key=None,
        refresh_token_expire_hours=None,
        redirect_url=url,
        owner=config.WEBMASTER_EMAIL,
    )
    if refresh:
        app.set_refresh_key(jwk.JWK.generate(kty="EC", size=4096))
        app.refresh_token_expire_hours = refresh_token_expire_hours or 24
    app.set_key(key)
    asyncio.run(app.insert())
    print(app_id)
Пример #11
0
async def test_verify_refresh_token_expired_token(
    fake_email,
    fake_refresh_client_app: ClientApp,
    monkeypatch,
    pwd_context,
    create_fake_queryset,
):
    uid = "fake_uuid"
    expires = datetime.datetime.now() + datetime.timedelta(hours=24)
    payload = {
        "iss": f"{config.ISSUER}/{fake_refresh_client_app.app_id}",
        "sub": fake_email,
        "uid": uid,
    }
    refresh_token = jwt.generate_jwt(
        payload,
        fake_refresh_client_app.get_refresh_key(),
        "ES256",
        datetime.timedelta(seconds=0),
    )
    saved_refresh_token = RefreshToken(
        app_id=fake_refresh_client_app.app_id,
        email=fake_email,
        hash=pwd_context.hash(refresh_token),
        expires=expires,
        uid=uid,
    )

    def _fake_query(*_args):
        return create_fake_queryset(get_return=saved_refresh_token)

    monkeypatch.setattr("app.security.token.RefreshToken.query", _fake_query)

    with pytest.raises(security_token.TokenVerificationError):
        await security_token.verify_refresh_token(refresh_token,
                                                  fake_refresh_client_app)
Пример #12
0
def export_public_key(client_app: ClientApp) -> dict:
    return client_app.get_key().export_public(as_dict=True)
Пример #13
0
def verify(token: str, client_app: ClientApp) -> (dict, dict):
    return _check_token(token, client_app.get_key(), client_app.app_id)
Пример #14
0
async def delete_refresh_token(refresh_token: str, client_app: ClientApp):
    _, claims = _check_token(refresh_token, client_app.get_refresh_key(),
                             client_app.app_id)
    found_rt = await _find_refresh_token(claims, client_app)
    await found_rt.delete()
Пример #15
0
async def check_refresh_client_app(client_app: ClientApp = Depends(check_client_app)):
    if not client_app.get_refresh_key():
        raise HTTPException(
            status_code=403, detail="Refreshing isn't supported on this app"
        )
    return client_app