__init__.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. """
  2. FlexMeasures API routes and implementations.
  3. """
  4. from flask import Flask, Blueprint, request
  5. from flask_security.utils import verify_password
  6. from flask_json import as_json
  7. from flask_login import current_user
  8. from sqlalchemy.exc import IntegrityError
  9. from sqlalchemy import select
  10. from flexmeasures.data import db
  11. from flexmeasures import __version__ as flexmeasures_version
  12. from flexmeasures.api.common.utils.api_utils import catch_timed_belief_replacements
  13. from flexmeasures.data.models.user import User
  14. from flexmeasures.api.common.utils.args_parsing import (
  15. validation_error_handler,
  16. )
  17. from flexmeasures.api.common.responses import invalid_sender
  18. from flexmeasures.data.schemas.utils import FMValidationError
  19. # The api blueprint. It is registered with the Flask app (see app.py)
  20. flexmeasures_api = Blueprint("flexmeasures_api", __name__)
  21. @flexmeasures_api.route("/requestAuthToken", methods=["POST"])
  22. @as_json
  23. def request_auth_token():
  24. """API endpoint to get a fresh authentication access token. Be aware that this fresh token has a limited lifetime
  25. (which depends on the current system setting SECURITY_TOKEN_MAX_AGE).
  26. Pass the `email` parameter to identify the user.
  27. Pass the `password` parameter to authenticate the user (if not already authenticated in current session)
  28. .. :quickref: Public; Obtain an authentication token
  29. """
  30. """
  31. The login of flask security returns the auth token, as well, but we'd like to
  32. * skip authentication if the user is authenticated already
  33. * be exempt from csrf protection (this is a JSON-only endpoint)
  34. * use a more fitting name inside the api namespace
  35. * return the information in a nicer structure
  36. """
  37. try:
  38. if not request.is_json:
  39. return {"errors": ["Content-type of request must be application/json"]}, 400
  40. if "email" not in request.json:
  41. return {"errors": ["Please provide the 'email' parameter."]}, 400
  42. email = request.json["email"]
  43. if current_user.is_authenticated and current_user.email == email:
  44. user = current_user
  45. else:
  46. user = db.session.execute(
  47. select(User).filter_by(email=email)
  48. ).scalar_one_or_none()
  49. if not user:
  50. return (
  51. {"errors": ["User with email '%s' does not exist" % email]},
  52. 404,
  53. )
  54. if "password" not in request.json:
  55. return {"errors": ["Please provide the 'password' parameter."]}, 400
  56. if not verify_password(request.json["password"], user.password):
  57. return {"errors": ["User password does not match."]}, 401
  58. token = user.get_auth_token()
  59. return {"auth_token": token, "user_id": user.id}
  60. except Exception as e:
  61. return {"errors": [str(e)]}, 500
  62. @flexmeasures_api.route("/", methods=["GET"])
  63. @as_json
  64. def get_versions() -> dict:
  65. """Public endpoint to list API versions.
  66. .. :quickref: Public; List available API versions
  67. """
  68. response = {
  69. "message": "For these API versions a public endpoint is available, listing its service. For example: "
  70. "/api/v3_0. An authentication token can be requested at: "
  71. "/api/requestAuthToken",
  72. "versions": ["v3_0"],
  73. "flexmeasures_version": flexmeasures_version,
  74. }
  75. return response
  76. def register_at(app: Flask):
  77. """This can be used to register this blueprint together with other api-related things"""
  78. # handle API specific errors
  79. app.register_error_handler(FMValidationError, validation_error_handler)
  80. app.register_error_handler(IntegrityError, catch_timed_belief_replacements)
  81. app.unauthorized_handler_api = invalid_sender
  82. app.register_blueprint(
  83. flexmeasures_api, url_prefix="/api"
  84. ) # now registering the blueprint will affect all endpoints
  85. # Load API endpoints for internal operations
  86. from flexmeasures.api.common import register_at as ops_register_at
  87. ops_register_at(app)
  88. # Load API endpoints for play mode
  89. if app.config.get("FLEXMEASURES_MODE", "") == "play":
  90. from flexmeasures.api.play import register_at as play_register_at
  91. play_register_at(app)
  92. # Load all versions of the API functionality
  93. from flexmeasures.api.v3_0 import register_at as v3_0_register_at
  94. from flexmeasures.api.dev import register_at as dev_register_at
  95. from flexmeasures.api.sunset import register_at as sunset_register_at
  96. v3_0_register_at(app)
  97. dev_register_at(app)
  98. sunset_register_at(app)