Forgot Password Service in Badgeyay

Badgeyay is an open source badge generator service for generating badges developed by FOSSASIA community for technical events and conferences. The project is divided into two components mainly frontend and backend. After creating the user registration functionality in application, if the user forgets the credentials for the login, then there must be a way to recreate the credentials using a secure channel. This is only valid for the users signed up through email login as for the case of OAuth they must have access to their ID on respective social platform. The main challenges in resetting password for the user is to provide a secure channel. So the problem can be breakdown into following issues: Creating a token for reset action Sending that token via mail to user Verifying that token on the server and giving access Changing the credentials  of the user Procedure Generating token for the request to change credentials for the user. The token will be an expiry token and will be expired in the mentioned duration. So the token is valid for only a limited period of time and will prevent fraudulent requests. def pwd_reset_token():   data = request.get_json()['data']['attributes']   if 'email' not in data.keys():       print('Email not found')   email = data['email']   user = User.getUser(email=email)   if not user:       return ErrorResponse(UserNotFound().message, 422, {'Content-Type': 'application/json'}).respond()   expire = datetime.datetime.utcnow() + datetime.timedelta(seconds=900)   token = jwt.encode({       'id': user.id,       'exp': expire   }, app.config.get('SECRET_KEY'))   resetObj = ResetPasswordToken(user.id, token.decode('UTF-8'))   resetObj.save_to_db()   return jsonify(TokenSchema().dump(resetObj).data) Model for ResetPasswordToken class ResetPasswordToken(db.Model):   __tablename__ = 'Reset Password Token'   id = db.Column(db.String, primary_key=True)   token = db.Column(db.String, nullable=False)   def __init__(self, uid, token):       self.id = uid       self.token = token   def save_to_db(self):       try:           db.session.add(self)           db.session.commit()       except Exception as e:           db.session.rollback()           db.session.flush()           print(e)   Sending the password reset link via mail to the user. The link will contain the token (expiry token) that will be used to validate the request. For the case we will be using Firebase Cloud functions as an HTTP Trigger. exports.sendResetMail = functions.https.onRequest((req, res) => { let token = req.query['token']; let email = req.query['email']; res.setHeader('Content-Type', 'application/json'); sendResetMail(token, email)   .then(() => {     console.log('Reset mail sent to', email);     res.json({ data: { attributes: { status: 200 }, id: token, type: 'reset-mails' } });     return 0;   })   .catch(err => {     console.error(err);     res.json({ data: { attributes: { status: 500 }, id: token, type: 'reset-mails' } });     return -1;   }); }); function sendResetMail(token, email) { const mailOptions = {   from: `${APP_NAME}<noreply@firebase.com>`,   to: email, }; mailOptions.subject = `Password reset link`; mailOptions.html = '<p>Hey ' + email + '! Here is your password reset <a href=\'' + PASSWORD_RESET_LINK   + token + '\'>Link</a><p>'; return mailTransport.sendMail(mailOptions); }   Verifying the token on the server side to validate the user request def validate_reset_token():   args = request.args   if 'token' in args.keys():       token = args.get('token')   resp = {'id': token}   try:       jwt.decode(token, app.config['SECRET_KEY'])       resp['valid'] = True       return jsonify(ValidTokenSchema().dump(resp).data)   except Exception as e:       resp['valid'] = False       print(e)       return jsonify(ValidTokenSchema().dump(resp).data)   After user has access to change the credentials, then user can send a POST request to backend through a form shown in UI to change its password. def changePwd():…

Continue ReadingForgot Password Service in Badgeyay

Metadata Updation in Badgeyay

Badgeyay is a simple badge generator service to develop badges for technical events and conferences developed by FOSSASIA. Badgeyay is a SPA (Single Page Application) developed in ember, whose backend is in Flask. Now when user logins, he can see an option for user profile, in which all the metadata of its profile can be seen (extracted from Firebase). Now user should be able to change its metadata like profile image and username etc. So we will look how the profile image is being changed and updated in badgeyay. Procedure Create function in frontend to listen for onclick events and initiate a file upload dialog box for selecting an image. We will use document property to initiate a dummy click event, else there will be a button with the text to upload a file and that won’t look consistent as we only need an image and nothing else on the UI. class="ui small circular image profile-image">     "{{user.photoURL}}">     "display: none;" id="profileImageSelector" type="file" onchange={{action "profileImageSelected"}}>     "profile-change" onclick={{action "updateProfileImage"}}>Change </div>   Function to upload file and initiate a dummy click event updateProfileImage() {     // Initate a dummy click event     document.getElementById('profileImageSelector').click();   },   profileImageSelected(event) {     const reader = new FileReader();     const { target } = event;     const { files } = target;     const [file] = files;     const _this = this;     reader.onload = () => {       _this.get('sendProfileImage')(reader.result, file.type.split('/')[1]);     };     reader.readAsDataURL(file);   }   Profile update function in the main controller to call the API endpoint to upload the data to backend. This will send the payload to backend which will later upload the image to cloud storage and save in the link in the database. updateProfileImage(profileImageData, extension) {     const _this = this;     const user = this.get('store').peekAll('user');     user.forEach(user_ => {       _this.set('uid', user_.get('id'));     });     let profileImage = _this.get('store').createRecord('profile-image', {       image   : profileImageData,       uid   : _this.uid,       extension : '.' + extension     });     profileImage.save()       .then(record => {         user.forEach(user_ => {           user_.set('photoURL', record.photoURL);         });       })       .catch(err => {         let userErrors = profileImage.get('errors.user');         if (userErrors !== undefined) {           _this.set('userError', userErrors);         }       });   } Route to update profile image from backend @router.route('/profileImage', methods=['POST']) def update_profile_image():   try:       data = request.get_json()['data']['attributes']   except Exception:       return ErrorResponse(PayloadNotFound().message, 422, {'Content-Type': 'application/json'}).respond()   if not data['image']:       return ErrorResponse(ImageNotFound().message, 422, {'Content-Type': 'application/json'}).respond()   if not data['extension']:       return ErrorResponse(ExtensionNotFound().message, 422, {'Content-Type': 'application/json'}).respond()   uid = data['uid']   image = data['image']   extension = data['extension']   try:       imageName = saveToImage(imageFile=image, extension=extension)   except Exception:       return ErrorResponse(ImageNotFound().message, 422, {'Content-Type': 'application/json'}).respond()   fetch_user, imageLink = update_database(uid, imageName)   return jsonify(UpdateUserSchema().dump(fetch_user).data)   This will first create a temp file with the data URI and them upload that file to cloud storage and generate the link and then update the user in the database. def update_database(uid, imageName):   fetch_user = User.getUser(user_id=uid)   if fetch_user is None:       return ErrorResponse(UserNotFound(uid).message, 422, {'Content-Type': 'application/json'}).respond()   imagePath = os.path.join(app.config.get('BASE_DIR'), 'static', 'uploads', 'image') + '/' + imageName   imageLink = fileUploader(imagePath, 'profile/images/' + imageName)   fetch_user.photoURL = imageLink   fetch_user.save_to_db()   try:       os.unlink(imagePath)   except Exception:       print('Unable to delete the temporary file')   return fetch_user, imageLink   Link to PR - Link Topics Involved Google Cloud Admin Storage SDK Ember data Resources Firebase admin sdk documentation - Link Google Cloud…

Continue ReadingMetadata Updation in Badgeyay

Uploading Badges To Google Cloud Badgeyay

Badgeyay is an open source project developed by FOSSASIA community. This project mainly aims for generating badges for technical conferences and events.The project is divided into two parts mainly. Backend is developed in flask and frontend is developed in emberjs. The problem is after the badge generation, the flask server is storing and serving those files. In practise this is not a good convention to do so. This should be handled by secondary hosting server program like gunicorn or nginx. Better approach would be to consume the firebase storage and admin sdk for storing the badges on google cloud. This will also offload storage needs from the flask server and also give a public link over the network to access. Procedure Get the file path of the temporary badge generated on the flask server. Currently badges are saved in the directory of the image file uploaded and final badge generated is written in all-badges.pdf` badgePath = os.getcwd() + '/static/temporary/' + badgeFolder badgePath + '/all-badges.pdf'   Create the blob path for the storage. Blob can be understood as the final reference to the location where the contents are saved onto the server. This can be a nested directory structure or simply a filename in root directory. 'badges/' + badge_created.id + '.pdf'   In our case it is the id of the badge that is generated in the badges directory. Function for uploading the file generated in temporary storage to google cloud storage. def fileUploader(file_path, blob_path):   bucket = storage.bucket()   fileUploaderBlob = bucket.blob(blob_path)   try:       with open(file_path, 'rb') as file_:           fileUploaderBlob.upload_from_file(file_)   except Exception as e:       print(e)   fileUploaderBlob.make_public()   return fileUploaderBlob.public_url   It creates a bucket using the firebase admin SDK and then open the file from the file path. After opening the file from the path it writes the data to the cloud storage. After the data is written, the blob is made public and the public access link to the blob is fetched, which then later returned and saved in the local database. Topics Involved Firebase admin sdk for storage Google cloud storage sdk Resources Firebase admin sdk documentation - Link Google Cloud Storage SDK Python - Link Blob Management - Link  

Continue ReadingUploading Badges To Google Cloud Badgeyay

Exporting CSV data through API

A Badge generator like Badgeyay must be able to generate, store and export the user data as and when needed. This blog post is about adding the exporting functionality to badgeyay backend.. Why do we need such an API? Exporting data is required for a user. A user may want to know the details he/she has uploaded to the system or server. In our case we are dealing with the fact of exporting the CSV data from backend of Badgeyay. Adding the functionality to backend Let us see how we implemented this functionality into the backend of the project. Step 1 : Adding the necessary imports We first need to import the required dependencies for the route to work import os import base64 import uuid from flask import request, Blueprint, jsonify from flask import current_app as app from api.models.file import File from api.schemas.file import ExportFileSchema from api.utils.errors import ErrorResponse from api.schemas.errors import FileNotFound Step 2 : Adding a route This step involves adding a separate route that provides us with the exported data from backend. @router.route('/csv/data', methods=['GET']) def export_data(): input_data = request.args file = File().query.filter_by(filename=input_data.get('filename')).first() if file is None: return ErrorResponse(FileNotFound(input_data.get('filename')).message, 422, {'Content-Type': 'application/json'}).respond() export_obj = { 'filename': file.filename, 'filetype': file.filetype, 'id': str(uuid.uuid4()), 'file_data': None} with open(os.path.join(app.config.get('BASE_DIR'), 'static', 'uploads', 'csv', export_obj['filename']), "r") as f: export_obj['file_data'] = f.read() export_obj['file_data'] = base64.b64encode(export_obj['file_data'].encode()) return jsonify(ExportFileSchema().dump(export_obj).data) Step 2 : Adding a relevant Schema After creating a route we need to add a relevant schema that will help us to deliver the badges generated by the user to the Ember JS frontend so that it can be consumed as JSON API objects and shown to the user. class ExportFileSchema(Schema): class Meta: type_ = 'export-data' kwargs = {'id': '<id>'} id = fields.Str(required=True, dump_only=True) filename = fields.Str(required=True, dump_only=True) filetype = fields.Str(required=True, dump_only=True) file_data = fields.Str(required=True, dump_only=True) This is the ExportFileSchema that produces the output results of the GET request on the route. This helps us get the data onto the frontend. Further Improvements We are working on making badgeyay more comprehensive yet simple. This API endpoint needs to get registered onto the frontend. This can be a further improvement to the project and can be iterated over the next days. Resources The Pull Request for the same : https://github.com/fossasia/badgeyay/pull/1138 The Issue for the same : https://github.com/fossasia/badgeyay/issues/1137 Read about adding routes Blueprint : http://flask.pocoo.org/docs/1.0/blueprints/ Read about Schemas : https://github.com/marshmallow-code/marshmallow-jsonapi

Continue ReadingExporting CSV data through API

Dated queries in Badgeyay admin

Badgeyay is not just an anonymous badge generator that creates badges according to your needs, but it now has an admin section that allows the admin of the website to control and look over the statistics of the website. Why do we need such an API? For an admin, one of the most common functionality is to gather the details of the users or the files being served onto or over the server. Not just that, but the admin must also be aware about the traffic or files on the server in a particular duration of time. So we need an API that can coordinate all the stuff that requires dated queries from the backend database. Adding the functionality to backend Let us see how we implemented this functionality into the backend of the project. Step 1 : Adding a route This step involves adding a separate route that provides us with the output of the dated badges queries from backend. @router.route('/get_badges_dated', methods=['POST']) def get_badges_dated(): schema = DatedBadgeSchema() input_data = request.get_json() data, err = schema.load(input_data) if err: return jsonify(err) dated_badges = Badges.query.filter(Badges.created_at <= data.get('end_date')).filter(Badges.created_at >= data.get('start_date')) return jsonify(AllBadges(many=True).dump(dated_badges).data) This route allows us to get badges produced by any user during a certain duration as a JSON API data object. This object is fed to the frontend to render the badges as cards. Step 2 : Adding a relevant Schema After creating a route we need to add a relevant schema that will help us to deliver the badges generated by the user to the Ember JS frontend so that it can be consumed as JSON API objects and shown to the user. class DatedBadgeSchema(Schema): class Meta: type_ = 'dated-badges' kwargs = {'id': '<id>'} id = fields.Str(required=True, dump_only=True) start_date = fields.Date(required=True) end_date = fields.Date(required=True) class AllBadges(Schema): class Meta: type_ = 'all-badges' self_view = 'admin.get_all_badges' kwargs = {'id': '<id>'} id = fields.Str(required=True, dump_only=True) image = fields.Str(required=True) csv = fields.Str(required=True) badge_id = fields.Str(required=True) text_color = fields.Str(required=True) badge_size = fields.Str(required=True) created_at = fields.Date(required=True) user_id = fields.Relationship( self_url='/api/upload/get_file', self_url_kwargs={'file_id': '<id>'}, related_url='/user/register', related_url_kwargs={'id': '<id>'}, include_resource_linkage=True, type_='User' ) This is the DatedBadge schema that produces the output results of the POST request on the route. And there is the AllBadges schema that produces the output results of the POST request on the route. Further Improvements We are working on adding multiple routes and adding modifications to database models and schemas so that the functionality of Badgeyay can be extended to a large extent. This will help us in making this badge generator even better. Resources The Pull Request for the same : https://github.com/fossasia/badgeyay/pull/1040 The Issue for the same : https://github.com/fossasia/badgeyay/issues/1039 Read about adding routes Blueprint : http://flask.pocoo.org/docs/1.0/blueprints/ Read about Schemas : https://github.com/marshmallow-code/marshmallow-jsonapi  

Continue ReadingDated queries in Badgeyay admin

Get My Badges from Badgeyay API

Badgeyay is no longer a simple badge generator. It has more cool features than before. Badgeyay now supports a feature that shows your badges. It is called ‘my-badges’ component. To get this component work, we need to design a backend API to deliver the badges produced by a particular user. Why do we need such an API? The main aim of Badgeyay has changed from being a standard and simple badge generator to a complete suite that solves your badge generation and management problem. So to tackle the problem of managing the produced badges per user, we need to define a separate route and schema that delivers the generated badges. Adding the functionality to backend Let us see how we implemented this functionality into the backend of the project. Step 1 : Adding a route This step involves adding a separate route that provides with the generated output of the badges linked with the user account. @router.route('/get_badges', methods=['GET']) def get_badges(): input_data = request.args user = User.getUser(user_id=input_data.get('uid')) badges = Badges().query.filter_by(creator=user) return jsonify(UserBadges(many=True).dump(badges).data) This route allows us to get badges produced by the user as a JSON API data object. This object is fed to the frontend to render the badges as cards. Step 2 : Adding a relevant Schema After creating a route we need to add a relevant schema that will help us to deliver the badges generated by the user to the Ember JS frontend so that it can be consumed as JSON API objects and shown to the user. class UserBadges(Schema): class Meta: type_ = 'user-badges' self_view = 'generateBadges.get_badges' kwargs = {'id': '<id>'} id = fields.Str(required=True, dump_only=True) image = fields.Str(required=True) csv = fields.Str(required=True) badge_id = fields.Str(required=True) text_color = fields.Str(required=True) badge_size = fields.Str(required=True) user_id = fields.Relationship( self_url='/api/upload/get_file', self_url_kwargs={'file_id': '<id>'}, related_url='/user/register', related_url_kwargs={'id': '<id>'}, include_resource_linkage=True, type_='User' ) This is the ‘UserBadge’ schema that produces the output results of the GET request on the route. Finally, once this is done we can fire up a GET request on our deployment to receive results. The command that you need to run is given below. $ ~ curl -X GET http://localhost:5000/api/get_badges?uid={user_id} Further Improvements We are working on adding multiple routes and adding modifications to database models and schemas so that the functionality of Badgeyay can be extended to a large extent. This will help us in making this badge generator even better. Resources The Pull Request for the same : https://github.com/fossasia/badgeyay/pull/949 The Issue for the same : https://github.com/fossasia/badgeyay/issues/948 Read about adding routes Blueprint : http://flask.pocoo.org/docs/1.0/blueprints/ Read about Schemas : https://github.com/marshmallow-code/marshmallow-jsonapi  

Continue ReadingGet My Badges from Badgeyay API

Custom Colored Images with Badgeyay

Backend functionality of any Badge generator is to generate badges as per the requirements of the user. Currently Badgeyay is capable of generating badges by the following way: Adding or Selecting a Pre-defined Image from the given set Uploading a new image and then using it as a background Well, badgeyay has been missing a functionality of generating Custom Colored images. What is meant by Custom Colored Badges? Currently, there are a set of 7 different kind of pre-defined images to choose from. But let’s say that a user want to choose from the images but doesn’t like any of the color. Therefore we provide the user with an additional option of applying custom background-color for their badges. This allows Badgeyay to deliver a more versatile amount of badges than ever before. Adding the functionality to backend Lets see how this functionality has been implemented in the backend of the project. Step 1 :  Adding a background-color route to backend Before generating badges, we need to know that what is the color that the user wants on the badge. Therefore we created a route that gathers the color and saves the user-defined.svg into that particular color. @router.route('/background_color', methods=['POST']) def background_color(): try: data = request.get_json()['data']['attributes'] bg_color = data['bg_color'] except Exception: return ErrorResponse(PayloadNotFound().message, 422, {'Content-Type': 'application/json'}).respond() svg2png = SVG2PNG() bg_color = '#' + str(bg_color) user_defined_path = svg2png.do_svg2png(1, bg_color) with open(user_defined_path, "rb") as image_file: image_data = base64.b64encode(image_file.read()) os.remove(user_defined_path) try: imageName = saveToImage(imageFile=image_data.decode('utf-8'), extension=".png") except Exception: return ErrorResponse(ImageNotFound().message, 422, {'Content-Type': 'application/json'}).respond() uid = data['uid'] fetch_user = User.getUser(user_id=uid) if fetch_user is None: return ErrorResponse(UserNotFound(uid).message, 422, {'Content-Type': 'application/json'}).respond() file_upload = File(filename=imageName, filetype='image', uploader=fetch_user) file_upload.save_to_db() return jsonify(ColorImageSchema().dump(file_upload).data) Step 2: Adding Schema for background-color to backend To get and save values from and to database, we need to have some layer of abstraction and so we use schemas created using marshmallow_jsonapi class ColorImageSchema(Schema): class Meta: type_ = 'bg-color' self_view = 'fileUploader.background_color' kwargs = {'id': '<id>'} id = fields.Str(required=True, dump_only=True) filename = fields.Str(required=True) filetype = fields.Str(required=True) user_id = fields.Relationship( self_url='/api/upload/background_color', self_url_kwargs={'file_id': '<id>'}, related_url='/user/register', related_url_kwargs={'id': '<id>'}, include_resource_linkage=True, type_='User' ) Now we have our schema and route done, So we can move forward with the logic of making badges. Step 3 : Converting the SVG to PNG and adding custom color Now we have the user-defined color for the badge background, but we still need a way to apply it to the badges. It is done using the following code below. def do_svg2png(self, opacity, fill): """ Module to convert svg to png :param `opacity` - Opacity for the output :param `fill` -  Background fill for the output """ filename = os.path.join(self.APP_ROOT, 'svg', 'user_defined.svg') tree = parse(open(filename, 'r')) element = tree.getroot() # changing style using XPath. path = element.xpath('//*[@id="rect4504"]')[0] style_detail = path.get("style") style_detail = style_detail.split(";") style_detail[0] = "opacity:" + str(opacity) style_detail[1] = "fill:" + str(fill) style_detail = ';'.join(style_detail) path.set("style", style_detail) # changing text using XPath. path = element.xpath('//*[@id="tspan932"]')[0] # Saving in the original XML tree etree.ElementTree(element).write(filename, pretty_print=True) print("done") png_name = os.path.join(self.APP_ROOT, 'static', 'uploads', 'image', str(uuid.uuid4())) + ".png" svg2png(url=filename, write_to=png_name) return png_name Finally…

Continue ReadingCustom Colored Images with Badgeyay

Paypal Integration in Open Event Server

The Open Event Server enables organizers to manage events from concerts to conferences and meetups. It offers features for events with several tracks and venues. This blog post explains how Paypal has been integrated in the Open Event Server in order to accept payments for tickets. The integration of Paypal in the server involved the following steps: An endpoint to accept the Paypal token from the client applications. Using the token to get the approved payment details. Capturing the payment using the fetched payment details. Endpoint for Paypal token The server exposes an endpoint to get the Paypal token in order to accept payments. api.route(ChargeList, 'charge_list', '/orders/<identifier>/charge', '/orders/<order_identifier>/charge') The above endpoint accepts the Paypal token and uses that to get the payment details from Paypal and then capture the payments. Getting Approved Payment Details We use the Paypal Name-Value pair API in the project. First we get the credentials of the event organizer who will be accepting the payments using a call to the get_credentials helper method. It returns the data as the following dictionary: credentials = { 'USER': settings['paypal_live_username'], 'PWD': settings['paypal_live_password'], 'SIGNATURE': settings['paypal_live_signature'], 'SERVER': 'https://api-3t.paypal.com/nvp', 'CHECKOUT_URL': 'https://www.paypal.com/cgi-bin/webscr', 'EMAIL': '' if not event or not event.paypal_email or event.paypal_email == "" else event.paypal_email } Next, we use the credentials to get the approved payment details from paypal using the following code snippet. @staticmethod def get_approved_payment_details(order, credentials=None): if not credentials: credentials = PayPalPaymentsManager.get_credentials(order.event) if not credentials: raise Exception('PayPal credentials have not been set correctly') data = { 'USER': credentials['USER'], 'PWD': credentials['PWD'], 'SIGNATURE': credentials['SIGNATURE'], 'SUBJECT': credentials['EMAIL'], 'METHOD': 'GetExpressCheckoutDetails', 'VERSION': PayPalPaymentsManager.api_version, 'TOKEN': order.paypal_token } if current_app.config['TESTING']: return data response = requests.post(credentials['SERVER'], data=data) return json.loads(response.text) Capturing the payments After successfully fetching the payment details, the final step is to capture the payment. We set the amount to be charged to the amount of the order and the payer_id to be the payer id received from step 2. Then we simply make a POST request to the Paypal nvp server and capture the payments. The below method is responsible for executing this task: @staticmethod def capture_payment(order, payer_id, currency=None, credentials=None): if not credentials: credentials = PayPalPaymentsManager.get_credentials(order.event) if not credentials: raise Exception('PayPal credentials have not be set correctly') if not currency: currency = order.event.payment_currency if not currency or currency == "": currency = "USD" data = { 'USER': credentials['USER'], 'PWD': credentials['PWD'], 'SIGNATURE': credentials['SIGNATURE'], 'SUBJECT': credentials['EMAIL'], 'METHOD': 'DoExpressCheckoutPayment', 'VERSION': PayPalPaymentsManager.api_version, 'TOKEN': order.paypal_token, 'PAYERID': payer_id, 'PAYMENTREQUEST_0_PAYMENTACTION': 'SALE', 'PAYMENTREQUEST_0_AMT': order.amount, 'PAYMENTREQUEST_0_CURRENCYCODE': currency, } response = requests.post(credentials['SERVER'], data=data) return json.loads(response.text) References Paypal NVP : https://developer.paypal.com/docs/classic/api/NVPAPIOverview/ Paypal Python SDK: https://github.com/paypal/PayPal-Python-SDK Flask REST JSON API: flask-rest-jsonapi

Continue ReadingPaypal Integration in Open Event Server

Charges Layer in Open Event Server

The Open Event Server enables organizers to manage events from concerts to conferences and meetups. It offers features for events with several tracks and venues. This blog post explains how the charge layer has been implemented in the Open Event Server in order to charge the user for tickets of an event. Schema We currently support payments via Stripe and Paypal. As a result the schema for Charges layer consists of fields for providing the token for stripe or paypal. It also contains a read only id field. class ChargeSchema(Schema): """ ChargeSchema """ class Meta: """ Meta class for ChargeSchema """ type_ = 'charge' inflect = dasherize self_view = 'v1.charge_list' self_view_kwargs = {'id': '<id>'} id = fields.Str(dump_only=True) stripe = fields.Str(allow_none=True) paypal = fields.Str(allow_none=True) Resource The ChargeList resource only supports POST requests since there is no need for other type of requests. We simply declare the schema, supported methods and the data layer. We also check for required permissions by declaring the decorators. class ChargeList(ResourceList): """ ChargeList ResourceList for ChargesLayer class """ methods = ['POST', ] schema = ChargeSchema data_layer = { 'class': ChargesLayer, 'session': db.session } decorators = (jwt_required,) Layer The data layer contains a single method create_object which does all the heavy lifting of charging the user according to the payment medium and the related order. It first loads the related order from the database using the identifier. We first check if the order contains one or more paid tickets or not. If not, then ConflictException is raised since it doesn’t make sense to charge a user without any paid ticket in the order. Next, it checks the payment mode of the order. If the payment mode is Stripe then it checks if the stripe_token is provided with the request or not. If not, an UnprocessableEntity exception is raised otherwise relevant methods are called in order to charge the user accordingly. A similar procedure is followed for payments via Paypal. Below is the full code for reference. class ChargesLayer(BaseDataLayer): def create_object(self, data, view_kwargs): """ create_object method for the Charges layer charge the user using paypal or stripe :param data: :param view_kwargs: :return: """ order = Order.query.filter_by(id=view_kwargs['id']).first() if not order: raise ObjectNotFound({'parameter': 'id'}, "Order with id: {} not found".format(view_kwargs['id'])) elif order.status == 'cancelled' or order.status == 'expired': raise ConflictException({'parameter': 'id'}, "You cannot charge payments on a cancelled or expired order") elif (not order.amount) or order.amount == 0: raise ConflictException({'parameter': 'id'}, "You cannot charge payments on a free order") # charge through stripe if order.payment_mode == 'stripe': if not data.get('stripe'): raise UnprocessableEntity({'source': ''}, "stripe token is missing") success, response = TicketingManager.charge_stripe_order_payment(order, data['stripe']) if not success: raise UnprocessableEntity({'source': 'stripe_token_id'}, response) # charge through paypal elif order.payment_mode == 'paypal': if not data.get('paypal'): raise UnprocessableEntity({'source': ''}, "paypal token is missing") success, response = TicketingManager.charge_paypal_order_payment(order, data['paypal']) if not success: raise UnprocessableEntity({'source': 'paypal'}, response) return order The charge_stripe_order_payment and charge_paypal_order_payment are helper methods defined to abstract away the complications of the procedure from the layer. References Flask REST JSON API: flask-rest-jsonapi SQLAlchemy: https://www.sqlalchemy.org/ Stripe connect: https://stripe.com/connect…

Continue ReadingCharges Layer in Open Event Server

Implementing Endpoint to Resend Email Verification

Earlier, when a user registered via Open Event Frontend, s/he received a verification link via email to confirm their account. However, this was not enough in the long-term. If the confirmation link expired, or for some reasons the verification mail got deleted on the user side, there was no functionality to resend the verification email, which prevented the user from getting fully registered. Although the front-end already showed the option to resend the verification link, there was no support from the server to do that, yet. So it was decided that a separate endpoint should be implemented to allow re-sending the verification link to a user. /resend-verification-email was an endpoint that would fit this action. So we decided to go with it and create a route in `auth.py` file, which was the appropriate place for this feature to reside. First step was to do the necessary imports and then definition: from app.api.helpers.mail import send_email_confirmation from app.models.mail import USER_REGISTER_WITH_PASSWORD ... ... @auth_routes.route('/resend-verification-email', methods=['POST']) def resend_verification_email(): ... Now we safely fetch the email mentioned in the request and then search the database for the user corresponding to that email: def resend_verification_email(): try: email = request.json['data']['email'] except TypeError: return BadRequestError({'source': ''}, 'Bad Request Error').respond() try: user = User.query.filter_by(email=email).one() except NoResultFound: return UnprocessableEntityError( {'source': ''}, 'User with email: ' + email + ' not found.').respond() else: ... Once a user has been identified in the database, we proceed further and create an essentially unique hash for the user verification. This hash is in turn used to generate a verification link that is then ready to be sent via email to the user: else: serializer = get_serializer() hash_ = str(base64.b64encode(str(serializer.dumps( [user.email, str_generator()])).encode()), 'utf-8') link = make_frontend_url( '/email/verify'.format(id=user.id), {'token': hash_}) Finally, the email is sent: send_email_with_action( user, USER_REGISTER_WITH_PASSWORD, app_name=get_settings()['app_name'], email=user.email) if not send_email_confirmation(user.email, link): return make_response(jsonify(message="Some error occured"), 500) return make_response(jsonify(message="Verification email resent"), 200) But this was not enough. When the endpoint was tested, it was found that actual emails were not being delivered, even after correctly configuring the email settings locally. So, after a bit of debugging, it was found that the settings, which were using Sendgrid to send emails, were using a deprecated Sendgrid API endpoint. A separate email function is used to send emails via Sendgrid and it contained an old endpoint that was no longer recommended by Sendgrid: @celery.task(name='send.email.post') def send_email_task(payload, headers): requests.post( "https://api.sendgrid.com/api/mail.send.json", data=payload, headers=headers ) The new endpoint, as per Sendgrid’s documentation, is: https://api.sendgrid.com/v3/mail/send But this was not the only change required. Sendgrid had also modified the structure of requests they accepted, and the new structure was different from the existing one that was used in the server. Following is the new structure: '{"personalizations": [{"to": [{"email": "example@example.com"}]}],"from": {"email": "example@example.com"},"subject": "Hello, World!","content": [{"type": "text/plain", "value": "Heya!"}]}' The header structure was also changed, so the structure in the server was also updated to headers = { "Authorization": ("Bearer " + key), "Content-Type": "application/json" } The Sendgrid function (which is executed as a Celery task) was modified as follows, to incorporate the changes…

Continue ReadingImplementing Endpoint to Resend Email Verification