Compare commits

...

18 Commits

Author SHA1 Message Date
lennyaiko d16978d023 chore 2025-04-03 18:25:00 +01:00
lennyaiko 5c38094c22 chore 2025-04-03 18:17:25 +01:00
lennyaiko 907b4d1f38 done 2025-04-03 18:16:13 +01:00
lennyaiko 5ff1f2c8bf done with JWT 2025-04-03 18:05:44 +01:00
lennyaiko 01b433e7e3 progress on JWT 2025-04-03 17:57:26 +01:00
lennyaiko a5f3119cdc progress on JWT 2025-04-03 17:54:29 +01:00
lennyaiko ec2ec07d60 progress on jwt 2025-04-03 17:42:54 +01:00
lennyaiko 76fc959e2e progress on jwt 2025-04-03 17:41:07 +01:00
lennyaiko 362f31c100 progress on jwt 2025-04-03 17:32:32 +01:00
lennyaiko a4df4912d6 progress on stuff 2025-04-03 17:14:09 +01:00
lennyaiko 87fe0fb5a0 progress on jwt 2025-04-03 17:11:37 +01:00
lennyaiko f6a3938901 progress on jwt 2025-04-03 17:04:39 +01:00
lennyaiko aac09bb3a1 progress on jwt 2025-04-03 17:00:10 +01:00
lennyaiko 19870edf73 progress on JWT 2025-04-03 16:54:14 +01:00
ameye 4f2f3cccb4 Merge branch 'bank-simbrella-calls' of DigiFi/digifi-BankToProductCore into master 2025-04-01 10:18:19 +00:00
CHIEFSOFT\ameye 41109d5353 Provide loan Request 2025-03-27 21:50:57 -04:00
CHIEFSOFT\ameye 35a54e98ee added server 2025-03-27 21:14:01 -04:00
ameye bcb7a27863 Merge branch 'bank-simbrella-calls' of DigiFi/digifi-BankToProductCore into master 2025-03-26 01:25:17 +00:00
18 changed files with 421 additions and 46 deletions
+3
View File
@@ -7,6 +7,9 @@ BASIC_AUTH_PASSWORD=******
SWAGGER_URL="/documentation" SWAGGER_URL="/documentation"
API_URL="/swagger.json" API_URL="/swagger.json"
JWT_SECRET_KEY=******
JWT_ACCESS_TOKEN_EXPIRES=******
JWT_REFRESH_TOKEN_EXPIRES=******
DATABASE_USER=***** DATABASE_USER=*****
+2 -1
View File
@@ -3,4 +3,5 @@ __pycache__/
app.log app.log
.DS_Store .DS_Store
migrations/__pycache__/ migrations/__pycache__/
migrations/*.pycg migrations/*.pycg
./vscode
+24
View File
@@ -0,0 +1,24 @@
{
"editor.lineNumbers": "off",
"editor.padding.top": 3,
"editor.padding.bottom": 3,
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.fontSize": 14,
"editor.lineHeight": 4.5,
"editor.suggestFontSize": 15,
// "editor.suggestLineHeight": 4,
"breadcrumbs.enabled": false,
"workbench.tips.enabled": false,
"workbench.statusBar.visible": false,
// "workbench.editor.showTabs": "single",
"git.enableSmartCommit": true,
"workbench.editor.editorActionsLocation": "hidden",
// "workbench.activityBar.location": "hidden",
"workbench.editor.enablePreviewFromQuickOpen": false,
"editor.lightbulb.enabled": "off",
"editor.selectionHighlight": false,
"editor.overviewRulerBorder": false,
"editor.renderLineHighlight": "none",
"editor.occurrencesHighlight": "off"
}
+13 -8
View File
@@ -8,38 +8,43 @@ from app.errors import register_error_handlers
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate from flask_migrate import Migrate
from app.extensions import db, migrate from app.extensions import db, migrate
from flask_jwt_extended import (
JWTManager,
jwt_required,
create_access_token,
get_jwt_identity,
)
def create_app(): def create_app():
""" Factory function to create a Flask app instance """ """Factory function to create a Flask app instance"""
app = Flask(__name__) app = Flask(__name__)
# Load configuration # Load configuration
app.config.from_object(Config) app.config.from_object(Config)
CORS(app)
CORS(app) JWTManager(app)
# Swagger Doc # Swagger Doc
SWAGGER_URL = app.config.get("SWAGGER_URL") SWAGGER_URL = app.config.get("SWAGGER_URL")
API_URL = app.config.get("API_URL") API_URL = app.config.get("API_URL")
# Register blueprints # Register blueprints
app.register_blueprint(api) app.register_blueprint(api)
swagger_ui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL) swagger_ui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL)
app.register_blueprint(swagger_ui_blueprint, url_prefix=SWAGGER_URL) app.register_blueprint(swagger_ui_blueprint, url_prefix=SWAGGER_URL)
# Error Handlers # Error Handlers
register_error_handlers(app) register_error_handlers(app)
from . import models from . import models
# Database and Migrations # Database and Migrations
db.init_app(app) db.init_app(app)
migrate.init_app(app, db) migrate.init_app(app, db)
return app return app
+48 -21
View File
@@ -6,11 +6,19 @@ from app.api.services import (
LoanStatusService, LoanStatusService,
RepaymentService, RepaymentService,
CustomerConsentService, CustomerConsentService,
NotificationCallbackService NotificationCallbackService,
AuthorizationService,
) )
from app.utils.logger import logger from app.utils.logger import logger
from app.api.middlewares import enforce_json, require_auth from app.api.middlewares import enforce_json, require_auth
import os import os
from flask_jwt_extended import (
JWTManager,
jwt_required,
create_access_token,
get_jwt_identity,
create_refresh_token,
)
api = Blueprint("api", __name__) api = Blueprint("api", __name__)
@@ -23,31 +31,31 @@ def cors_middleware():
# Swagger JSON file # Swagger JSON file
@api.route("/swagger.json", methods=['GET']) @api.route("/swagger.json", methods=["GET"])
def swagger_json(): def swagger_json():
swagger_dir = os.path.join("swagger") swagger_dir = os.path.join("swagger")
return send_from_directory(swagger_dir, "digifi_swagger.json") return send_from_directory(swagger_dir, "digifi_swagger.json")
@api.route("/swagger/<path:filename>")
@api.route('/swagger/<path:filename>')
def serve_paths(filename): def serve_paths(filename):
swagger_dir = os.path.join("swagger") swagger_dir = os.path.join("swagger")
return send_from_directory(swagger_dir, filename) return send_from_directory(swagger_dir, filename)
# EligibilityCheck Endpoint # EligibilityCheck Endpoint
@api.route('/EligibilityCheck', methods=['POST']) @api.route("/EligibilityCheck", methods=["POST"])
@require_auth @jwt_required()
def eligibility_check(): def eligibility_check():
data = request.get_json() data = request.get_json()
# logger.info(f"EligibilityCheck request received: {data}") # logger.info(f"EligibilityCheck request received: {data}")
response = EligibilityCheckService.process_request(data) response = EligibilityCheckService.process_request(data)
return response return response
# SelectOffer Endpoint # SelectOffer Endpoint
@api.route('/SelectOffer', methods=['POST']) @api.route("/SelectOffer", methods=["POST"])
@require_auth @jwt_required()
def select_offer(): def select_offer():
data = request.get_json() data = request.get_json()
# logger.info(f"SelectOffer request received: {data}") # logger.info(f"SelectOffer request received: {data}")
@@ -56,8 +64,8 @@ def select_offer():
# ProvideLoan Endpoint # ProvideLoan Endpoint
@api.route('/ProvideLoan', methods=['POST']) @api.route("/ProvideLoan", methods=["POST"])
@require_auth @jwt_required()
def provide_loan(): def provide_loan():
data = request.get_json() data = request.get_json()
# logger.info(f"ProvideLoan request received: {data}") # logger.info(f"ProvideLoan request received: {data}")
@@ -66,8 +74,8 @@ def provide_loan():
# LoanStatus Endpoint # LoanStatus Endpoint
@api.route('/LoanStatus', methods=['POST']) @api.route("/LoanStatus", methods=["POST"])
@require_auth @jwt_required()
def loan_status(): def loan_status():
data = request.get_json() data = request.get_json()
# logger.info(f"LoanStatus request received: {data}") # logger.info(f"LoanStatus request received: {data}")
@@ -76,8 +84,8 @@ def loan_status():
# Repayment Endpoint # Repayment Endpoint
@api.route('/Repayment', methods=['POST']) @api.route("/Repayment", methods=["POST"])
@require_auth @jwt_required()
def repayment(): def repayment():
data = request.get_json() data = request.get_json()
# logger.info(f"Repayment request received: {data}") # logger.info(f"Repayment request received: {data}")
@@ -86,8 +94,8 @@ def repayment():
# CustomerConsent Endpoint # CustomerConsent Endpoint
@api.route('/CustomerConsent', methods=['POST']) @api.route("/CustomerConsent", methods=["POST"])
@require_auth @jwt_required()
def customer_consent(): def customer_consent():
data = request.get_json() data = request.get_json()
# logger.info(f"CustomerConsent request received: {data}") # logger.info(f"CustomerConsent request received: {data}")
@@ -96,8 +104,8 @@ def customer_consent():
# NotificationCallback Endpoint # NotificationCallback Endpoint
@api.route('/NotificationCallback', methods=['POST']) @api.route("/NotificationCallback", methods=["POST"])
@require_auth @jwt_required()
def notification_callback(): def notification_callback():
data = request.get_json() data = request.get_json()
# logger.info(f"NotificationCallback request received: {data}") # logger.info(f"NotificationCallback request received: {data}")
@@ -106,6 +114,25 @@ def notification_callback():
# Health Check Endpoint # Health Check Endpoint
@api.route('/health', methods=['GET']) @api.route("/health", methods=["GET"])
def health_check(): def health_check():
return {"status": "ok"} , 200 return {"status": "ok"}, 200
# Authorize endpoint
@api.route("/Authorize", methods=["POST"])
def authorize():
data = request.get_json()
# logger.info(f"Authorize request received: {data}")
response = AuthorizationService.process_request(data)
return response
# Authorize refresh endpoint
@api.route("/AuthorizeRefresh", methods=["POST"])
@jwt_required(refresh=True)
def refresh():
data = request.get_json()
# logger.info(f"Authorize refresh request received: {data}")
response = AuthorizationService.process_refresh_request()
return response
+6
View File
@@ -0,0 +1,6 @@
from marshmallow import Schema, fields
class AuthorizeRequestSchema(Schema):
username = fields.Str(required=True)
password = fields.Str(required=True)
+2 -1
View File
@@ -4,4 +4,5 @@ from app.api.services.provide_loan import ProvideLoanService
from app.api.services.loan_status import LoanStatusService from app.api.services.loan_status import LoanStatusService
from app.api.services.repayment import RepaymentService from app.api.services.repayment import RepaymentService
from app.api.services.customer_consent import CustomerConsentService from app.api.services.customer_consent import CustomerConsentService
from app.api.services.notification_callback import NotificationCallbackService from app.api.services.notification_callback import NotificationCallbackService
from app.api.services.authorization import AuthorizationService
+102
View File
@@ -0,0 +1,102 @@
from flask import request, jsonify
from marshmallow import ValidationError
from app.api.services.base_service import BaseService
from app.utils.logger import logger
from app.api.schemas.authorization import AuthorizeRequestSchema
from app.api.helpers.response_helper import ResponseHelper
from flask_jwt_extended import (
JWTManager,
jwt_required,
create_access_token,
create_refresh_token,
get_jwt_identity,
)
from app.config import Config
USERNAME = Config.BASIC_AUTH_USERNAME
PASSWORD = Config.BASIC_AUTH_PASSWORD
class AuthorizationService(BaseService):
@staticmethod
def process_request(data):
"""
Process the Authorization request.
Args:
data (dict): The request data.
Returns:
dict: A standardized response.
"""
try:
logger.info("Processing Authorization request")
if not data:
return ResponseHelper.bad_request(message="Missing JSON in request")
# Validate input data using the Authorization schema
schema = AuthorizeRequestSchema()
validated_data = schema.load(data) # Raises ValidationError if invalid
if (
validated_data["username"] != USERNAME
or validated_data["password"] != PASSWORD
):
return ResponseHelper.unauthorized(message="Invalid credentials")
access_token = create_access_token(identity=validated_data["username"])
refresh_token = create_refresh_token(identity=validated_data["username"])
# Simulated processing logic
response_data = {
"access_token": access_token,
"refresh_token": refresh_token,
}
return ResponseHelper.success(
data=response_data, message="Authorization processed successfully"
)
except ValidationError as e:
logger.error(f"Validation error: {e}")
return ResponseHelper.bad_request(message=f"Validation error: {e}")
except Exception as e:
logger.error(f"Error processing Authorization request: {e}")
return ResponseHelper.internal_server_error(
message=f"Error processing Authorization request: {e}"
)
@staticmethod
def process_refresh_request():
"""
Process the RefreshToken request.
Args:
data (dict): The request data.
Returns:
dict: A standardized response.
"""
try:
logger.info("Processing RefreshToken request")
identity = get_jwt_identity()
access_token = create_access_token(identity=identity)
# Simulated processing logic
response_data = {
"access_token": access_token,
}
return ResponseHelper.success(
data=response_data, message="RefreshToken processed successfully"
)
except Exception as e:
logger.error(f"Error processing RefreshToken request: {e}")
return ResponseHelper.internal_server_error(
message=f"Error processing RefreshToken request: {e}"
)
+11 -6
View File
@@ -1,16 +1,17 @@
import os import os
from datetime import timedelta
class Config: class Config:
"""Base configuration for Flask app""" """Base configuration for Flask app"""
SWAGGER_URL = os.getenv("SWAGGER_URL", "/documentation") SWAGGER_URL = os.getenv("SWAGGER_URL", "/documentation")
API_URL = os.getenv("API_URL", "/swagger.json") API_URL = os.getenv("API_URL", "/swagger.json")
DEBUG = True DEBUG = True
VALID_APP_ID = os.getenv("VALID_APP_ID", "app1") VALID_APP_ID = os.getenv("VALID_APP_ID", "app1")
VALID_API_KEY = os.getenv("VALID_API_KEY", "test-api-key-12345") VALID_API_KEY = os.getenv("VALID_API_KEY", "test-api-key-12345")
BASIC_AUTH_USERNAME = os.environ.get("BASIC_AUTH_USERNAME", "user") BASIC_AUTH_USERNAME = os.environ.get("BASIC_AUTH_USERNAME", "user")
BASIC_AUTH_PASSWORD = os.environ.get("BASIC_AUTH_PASSWORD", "password") BASIC_AUTH_PASSWORD = os.environ.get("BASIC_AUTH_PASSWORD", "password")
DATABASE_USER = os.environ.get("DATABASE_USER") DATABASE_USER = os.environ.get("DATABASE_USER")
@@ -19,11 +20,15 @@ class Config:
DATABASE_PORT = os.environ.get("DATABASE_PORT", 10532) DATABASE_PORT = os.environ.get("DATABASE_PORT", 10532)
DATABASE_NAME = os.environ.get("DATABASE_NAME") DATABASE_NAME = os.environ.get("DATABASE_NAME")
SQLALCHEMY_DATABASE_URI = ( SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}"
f"postgresql+psycopg2://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}"
)
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
SIMBRELLA_BASE_URL = os.getenv("SIMBRELLA_BASE_URL", "http://127.0.0.1:6337") SIMBRELLA_BASE_URL = os.getenv("SIMBRELLA_BASE_URL", "http://127.0.0.1:6337")
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "secret-key")
JWT_ACCESS_TOKEN_EXPIRES = os.getenv("JWT_ACCESS_TOKEN_EXPIRES", timedelta(hours=1))
JWT_REFRESH_TOKEN_EXPIRES = os.getenv(
"JWT_REFRESH_TOKEN_EXPIRES", timedelta(days=30)
)
settings = Config()
settings = Config()
+48 -5
View File
@@ -1,7 +1,7 @@
{ {
"openapi": "3.0.3", "openapi": "3.0.3",
"info": { "info": {
"title": "Swagger Simbrella FirstAdvance - OpenAPI 3.0", "title": "Swagger Bank Channel to Simbrella FirstAdvance - OpenAPI 3.0",
"description": "This is a Simbrella FirstAdvance Backend Server with the OpenAPI 3.0 specification. \n\n\nSome useful links:\n- [Web Simulated Demo Page](https://digifi-salaryloan.chiefsoft.net/)\n- [Web Management Support Portal](https://digifi-office.chiefsoft.net/auth/login)", "description": "This is a Simbrella FirstAdvance Backend Server with the OpenAPI 3.0 specification. \n\n\nSome useful links:\n- [Web Simulated Demo Page](https://digifi-salaryloan.chiefsoft.net/)\n- [Web Management Support Portal](https://digifi-office.chiefsoft.net/auth/login)",
"termsOfService": "http://swagger.io/terms/", "termsOfService": "http://swagger.io/terms/",
"contact": { "contact": {
@@ -16,6 +16,9 @@
"servers": [ "servers": [
{ {
"url": "http://localhost:4500" "url": "http://localhost:4500"
},
{
"url": "http://api.dev.simbrellang.net:4500"
} }
], ],
"tags": [ "tags": [
@@ -74,6 +77,22 @@
"description": "Find out more", "description": "Find out more",
"url": "https://www.simbrellang.net" "url": "https://www.simbrellang.net"
} }
},
{
"name": "Authorize",
"description": "This feature will be used for authorizing customers.",
"externalDocs": {
"description": "Find out more",
"url": "https://www.simbrellang.net"
}
},
{
"name": "AuthorizeRefresh",
"description": "This feature will be used for refreshing authorized customers.",
"externalDocs": {
"description": "Find out more",
"url": "https://www.simbrellang.net"
}
} }
], ],
"paths": { "paths": {
@@ -97,6 +116,12 @@
}, },
"/NotificationCallback": { "/NotificationCallback": {
"$ref": "swagger/paths/NotificationCallback.json" "$ref": "swagger/paths/NotificationCallback.json"
},
"/Authorize": {
"$ref": "swagger/paths/Authorize.json"
},
"/AuthorizeRefresh": {
"$ref": "swagger/paths/AuthorizeRefresh.json"
} }
}, },
"components": { "components": {
@@ -139,18 +164,36 @@
}, },
"ApiResponse": { "ApiResponse": {
"$ref": "swagger/schemas/ApiResponse.json" "$ref": "swagger/schemas/ApiResponse.json"
},
"AuthorizeResponse": {
"$ref": "swagger/schemas/AuthorizeResponse.json"
},
"AuthorizeRequest": {
"$ref": "swagger/schemas/AuthorizeRequest.json"
},
"AuthorizeRefreshResponse": {
"$ref": "swagger/schemas/AuthorizeRefreshResponse.json"
},
"AuthorizeRefreshRequest": {
"$ref": "swagger/schemas/AuthorizeRefreshRequest.json"
} }
}, },
"securitySchemes": { "securitySchemes": {
"basicAuth": { "basicAuth": {
"type": "http", "type": "http",
"scheme": "basic" "scheme": "basic"
},
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
} }
} }
}, },
"security": [ "security": [
{ {
"basicAuth": [] "basicAuth": [],
"bearerAuth": []
} }
] ]
} }
+54
View File
@@ -0,0 +1,54 @@
{
"post": {
"tags": ["Authorize"],
"summary": "Customer Authorize Request",
"description": "Customer Authorize Request",
"operationId": "Authorize",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/AuthorizeRequest.json"
}
},
"application/xml": {
"schema": {
"$ref": "../schemas/AuthorizeRequest.json"
}
},
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "../schemas/AuthorizeRequest.json"
}
}
}
},
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/AuthorizeResponse.json"
}
},
"application/xml": {
"schema": {
"$ref": "../schemas/AuthorizeResponse.json"
}
}
}
},
"400": {
"description": "Invalid request parameters"
},
"422": {
"description": "Validation exception"
},
"500": {
"description": "Internal server error"
}
}
}
}
+54
View File
@@ -0,0 +1,54 @@
{
"post": {
"tags": ["Authorize Refresh"],
"summary": "Customer Authorize Refresh Request",
"description": "Customer Authorize Refresh Request",
"operationId": "AuthorizeRefresh",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/AuthorizeRefreshRequest.json"
}
},
"application/xml": {
"schema": {
"$ref": "../schemas/AuthorizeRefreshRequest.json"
}
},
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "../schemas/AuthorizeRefreshRequest.json"
}
}
}
},
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "../schemas/AuthorizeRefreshResponse.json"
}
},
"application/xml": {
"schema": {
"$ref": "../schemas/AuthorizeRefreshResponse.json"
}
}
}
},
"400": {
"description": "Invalid request parameters"
},
"422": {
"description": "Validation exception"
},
"500": {
"description": "Internal server error"
}
}
}
}
@@ -0,0 +1,7 @@
{
"type": "object",
"properties": {},
"xml": {
"name": "AuthorizeRefreshRequest"
}
}
@@ -0,0 +1,12 @@
{
"type": "object",
"properties": {
"access_token": {
"type": "string",
"example": "access_token"
}
},
"xml": {
"name": "AuthorizeRefreshResponse"
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"type": "object",
"properties": {
"username": {
"type": "string",
"example": "user"
},
"password": {
"type": "string",
"example": "password"
}
},
"xml": {
"name": "AuthorizeRequest"
}
}
@@ -0,0 +1,16 @@
{
"type": "object",
"properties": {
"access_token": {
"type": "string",
"example": "access_token"
},
"refresh_token": {
"type": "string",
"example": "refresh_token"
}
},
"xml": {
"name": "AuthorizeResponse"
}
}
@@ -1,10 +1,6 @@
{ {
"type": "object", "type": "object",
"properties": { "properties": {
"type": {
"type": "string",
"example": "ProvideLoanRequest"
},
"requestId": { "requestId": {
"type": "string", "type": "string",
"example": "202111170001371256908" "example": "202111170001371256908"
+3
View File
@@ -27,3 +27,6 @@ python-dotenv
# Requests # Requests
requests requests
# JWT
flask-jwt-extended