diff --git a/apps/challenges/views.py b/apps/challenges/views.py index 11f6df62f2..6c3c6c8dcc 100644 --- a/apps/challenges/views.py +++ b/apps/challenges/views.py @@ -3131,10 +3131,33 @@ def invite_users_to_challenge(request, challenge_pk): response_data = {"error": "Users email can't be blank"} return Response(response_data, status=status.HTTP_400_BAD_REQUEST) + # Parse emails safely using json.loads instead of dangerous eval() try: - users_email = eval(users_email) - except Exception: - response_data = {"error": "Invalid format for users email"} + # If users_email is already a list (from JSON request body), use it directly + if isinstance(users_email, list): + parsed_emails = users_email + # Otherwise, parse the JSON string + elif isinstance(users_email, str): + parsed_emails = json.loads(users_email) + else: + response_data = {"error": "Invalid email format. Expected a list of email addresses."} + return Response(response_data, status=status.HTTP_400_BAD_REQUEST) + + # Validate that the result is a list + if not isinstance(parsed_emails, list): + response_data = {"error": "Invalid email format. Expected a list of email addresses."} + return Response(response_data, status=status.HTTP_400_BAD_REQUEST) + + # Validate that all items in the list are strings + if not all(isinstance(email, str) for email in parsed_emails): + response_data = {"error": "All email addresses must be strings."} + return Response(response_data, status=status.HTTP_400_BAD_REQUEST) + + users_email = parsed_emails + + except (json.JSONDecodeError, TypeError, ValueError) as e: + logger.error(f"Error parsing email list: {str(e)}") + response_data = {"error": "Invalid JSON format for users email"} return Response(response_data, status=status.HTTP_400_BAD_REQUEST) invalid_emails = [] diff --git a/bower.json b/bower.json index 0b9d0ea662..a96a44cc3e 100644 --- a/bower.json +++ b/bower.json @@ -35,7 +35,8 @@ "moment-picker": "0.10.2", "angular-moment-picker": "moment-picker#^0.10.2", "moment-timezone": "^0.5.25", - "trix": "1.3.1" + "trix": "1.3.1", + "ngMeta": "vinaygopinath/ngMeta" }, "resolutions": { "angular": "1.7.0" diff --git a/frontend/app.scripts.json b/frontend/app.scripts.json index cb8d7e03c1..423d2e9a5d 100644 --- a/frontend/app.scripts.json +++ b/frontend/app.scripts.json @@ -24,7 +24,8 @@ "angular-moment", "clipboard", "ngclipboard", - "moment-picker" + "moment-picker", + "ngMeta" ] }, "paths": { @@ -51,6 +52,7 @@ "angular-moment": "bower_components/angular-moment/angular-moment.js", "clipboard": "bower_components/clipboard/dist/clipboard.min.js", "ngclipboard": "bower_components/ngclipboard/dist/ngclipboard.min.js", - "moment-picker": "bower_components/angular-moment-picker/dist/angular-moment-picker.js" + "moment-picker": "bower_components/angular-moment-picker/dist/angular-moment-picker.js", + "ngMeta": "bower_components/ngMeta/dest/ngMeta.min.js" } } diff --git a/frontend/base.html b/frontend/base.html index cf77da6193..af9fa55ba4 100644 --- a/frontend/base.html +++ b/frontend/base.html @@ -2,20 +2,23 @@ - + - + - - - + + + - + + + + diff --git a/frontend/src/js/app.js b/frontend/src/js/app.js index c63a8d1d3f..8595d64bc3 100644 --- a/frontend/src/js/app.js +++ b/frontend/src/js/app.js @@ -14,7 +14,8 @@ angular 'angularTrix', 'angularMoment', 'ngclipboard', - 'moment-picker' + 'moment-picker', + 'ngMeta' ]) .service('preventTemplateCache', [function() { var service = this; @@ -36,4 +37,12 @@ angular }]) .config(['$compileProvider', function($compileProvider) { $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|file|tel|javascript):/); + }]) + .config(['ngMetaProvider', function(ngMetaProvider) { + ngMetaProvider.useTitleSuffix(true); + ngMetaProvider.setDefaultTitle('EvalAI'); + ngMetaProvider.setDefaultTitleSuffix(' | EvalAI'); + ngMetaProvider.setDefaultTag('description', 'EvalAI is an open-source web platform for organizing and participating in challenges to push the state of the art on AI tasks.'); + ngMetaProvider.setDefaultTag('image', 'https://eval.ai/dist/images/evalai-cover.png'); + ngMetaProvider.setDefaultTag('url', 'https://eval.ai'); }]); diff --git a/frontend/src/js/controllers/challengeCtrl.js b/frontend/src/js/controllers/challengeCtrl.js index e03738f040..fa8984682b 100644 --- a/frontend/src/js/controllers/challengeCtrl.js +++ b/frontend/src/js/controllers/challengeCtrl.js @@ -6,9 +6,9 @@ .module('evalai') .controller('ChallengeCtrl', ChallengeCtrl); - ChallengeCtrl.$inject = ['utilities', 'loaderService', '$scope', '$state', '$http', '$stateParams', '$rootScope', '$interval', '$mdDialog', 'moment', '$location', '$anchorScroll', '$timeout']; + ChallengeCtrl.$inject = ['utilities', 'loaderService', '$scope', '$state', '$http', '$stateParams', '$rootScope', '$interval', '$mdDialog', 'moment', '$location', '$anchorScroll', '$timeout', 'ngMeta']; - function ChallengeCtrl(utilities, loaderService, $scope, $state, $http, $stateParams, $rootScope, $interval, $mdDialog, moment, $location, $anchorScroll, $timeout) { + function ChallengeCtrl(utilities, loaderService, $scope, $state, $http, $stateParams, $rootScope, $interval, $mdDialog, moment, $location, $anchorScroll, $timeout, ngMeta) { var vm = this; vm.areSubmissionsFailing = false; vm.getAllEntriesTestOption = "Include private submissions"; @@ -403,6 +403,12 @@ } + // Update Open Graph / Twitter meta tags for challenge sharing + ngMeta.setTitle(details.title); + ngMeta.setTag('description', details.short_description || details.description || ''); + ngMeta.setTag('image', vm.page.image); + ngMeta.setTag('url', window.location.href); + // Get challenge prizes vm.prizes = [];