def verifyEmailToken(self, request): """ Verify email token, to verify email address. Return email address string or 'error' """ status = StringMsg() # return status status.data = 'error' # default to error # Decode the JWT token try: payload = jwt.decode(request.accessToken, EMAIL_VERIF_SECRET, algorithm='HS256') except: return status # If valid JWT token, extract the info and update DB if applicable user_id = payload['userId'] email = payload['contactEmail'] profile_key = ndb.Key(Profile, user_id) profile = profile_key.get() # If user changed email and clicked on old email verif link, this request is invalid if profile.contactEmail != email: return status # If we get here then email is verified. Update DB and return successful status profile.emailVerified = True profile.put() status.data = email return status
def changePassword(self, request): """ Change password """ status = StringMsg() status.data = 'error' # Get user profile user_id = self._getUserId(request.accessToken) profile_key = ndb.Key(Profile, user_id) profile = profile_key.get() # Not sure how this would happen, but it would be an error if not profile: return status # Check if provided old password matches user's current password db_salt, db_passkey = profile.salt_passkey.split('|') passkey = KDF.PBKDF2(request.oldPw, db_salt.decode('hex')).encode('hex') # Passwords don't match, return if passkey != db_passkey: status.data = 'old_pw_wrong' return status # If passwords match, salt & hash new password new_salt = Crypto.Random.new().read(16) new_passkey = KDF.PBKDF2(request.newPw, new_salt).encode('hex') new_salt_passkey = new_salt.encode('hex') + '|' + new_passkey profile.salt_passkey = new_salt_passkey # Also generate new session ID session_id = Crypto.Random.new().read(16).encode('hex') profile.session_id = session_id # Update DB profile.put() # Send user an email to notify password change self._emailPwChange(profile) # Return success status status.data = 'success' return status
def createAccount(self, request): """ Create new custom account """ status = StringMsg() # return status status.data = 'error' # default to error # Verify if user passed reCAPTCHA # POST request to Google reCAPTCHA API url = 'https://www.google.com/recaptcha/api/siteverify?secret=%s&response=%s' % (GRECAPTCHA_SECRET, request.recaptcha) try: result = urlfetch.Fetch(url, method=2) except: raise endpoints.BadRequestException('urlfetch error: Unable to POST to Google reCAPTCHA') return status data = json.loads(result.content) if not data['success']: status.data = 'recaptcha_fail' return status user_id = 'ca_' + request.email # Get profile from datastore -- if profile not found, then profile=None profile_key = ndb.Key(Profile, user_id) profile = profile_key.get() # If profile exists, return status if profile: status.data = 'user_exists' return status # Salt and hash the password salt = Crypto.Random.new().read(16) passkey = KDF.PBKDF2(request.password, salt).encode('hex') salt_passkey = salt.encode('hex') + '|' + passkey # Generate new session ID session_id = Crypto.Random.new().read(16).encode('hex') # Create new profile for user Profile( key = profile_key, userId = user_id, contactEmail = request.email, salt_passkey = salt_passkey, session_id = session_id, loggedIn = True, emailVerified = False, notifications = [False, True] ).put() # Generate user access token token = self._genToken({'userId': user_id, 'session_id': session_id}) # If we get here, means we suceeded status.data = 'success' status.accessToken = token return status
def resetPassword(self, request): """ Reset password, verify token. Return status. """ status = StringMsg() status.data = 'error' # Validate and decode token try: payload = jwt.decode(request.accessToken, CA_SECRET, algorithm='HS256') except: status.data = 'invalid_token' return status # Get user profile user_id = payload['userId'] profile_key = ndb.Key(Profile, user_id) profile = profile_key.get() # Not sure how this would happen, but it would be an error if not profile: return status # Salt & hash new password new_salt = Crypto.Random.new().read(16) new_passkey = KDF.PBKDF2(request.data, new_salt).encode('hex') new_salt_passkey = new_salt.encode('hex') + '|' + new_passkey profile.salt_passkey = new_salt_passkey # Also generate new session ID session_id = Crypto.Random.new().read(16).encode('hex') profile.session_id = session_id # Update DB profile.put() # Send user an email to notify password change self._emailPwChange(profile) # Return success status status.data = 'success' return status
def _updateProfile(self, request): """Update user profile.""" status = StringMsg() status.data = 'normal' token = request.accessToken user_id = self._getUserId(token) # Make sure the incoming message is initialized, raise exception if not request.check_initialized() # Get existing profile from datastore profile_key = ndb.Key(Profile, user_id) profile = profile_key.get() # Check if email changed. Note custom account users cannot change email. email_change = (profile.contactEmail != request.contactEmail) and (user_id[:3] != 'ca_') # Update profile object from the user's form for field in request.all_fields(): if field.name == 'userId': continue # userId is fixed elif user_id[:3] == 'ca_' and field.name == 'contactEmail': continue # custom account users cannot change email address elif field.name != 'accessToken': setattr(profile, field.name, getattr(request, field.name)) # If this is user's first time updating profile, or changing email address # then send email verification if profile.pristine or email_change: profile.pristine = False self._emailVerif(profile) status.data = 'email_verif' # Save updated profile to datastore profile.put() return status
def login(self, request): """ Check username/password to login """ status = StringMsg() # return status status.data = 'error' # default to error # Verify if user passed reCAPTCHA # POST request to Google reCAPTCHA API url = 'https://www.google.com/recaptcha/api/siteverify?secret=%s&response=%s' % (GRECAPTCHA_SECRET, request.recaptcha) try: result = urlfetch.Fetch(url, method=2) except: raise endpoints.BadRequestException('urlfetch error: Unable to POST to Google reCAPTCHA') return status data = json.loads(result.content) if not data['success']: status.data = 'recaptcha_fail' return status user_id = 'ca_' + request.email # Get profile from datastore -- if profile not found, then profile=None profile_key = ndb.Key(Profile, user_id) profile = profile_key.get() # If profile does not exist, return False if not profile: return status # Parse salt and passkey from DB, compare it to provided version db_salt, db_passkey = profile.salt_passkey.split('|') passkey = KDF.PBKDF2(request.password, db_salt.decode('hex')).encode('hex') # Passwords don't match, return False if passkey != db_passkey: return status # Generate new session ID session_id = Crypto.Random.new().read(16).encode('hex') profile.session_id = session_id # Update user's status to logged-in profile.loggedIn = True profile.put() # Generate user access token token = self._genToken({'userId': user_id, 'session_id': session_id}) # If we get here, means we suceeded status.data = 'success' status.accessToken = token return status
def forgotPassword(self, request): """ Forgot password, send user password reset link via email """ status = StringMsg() status.data = 'error' # Verify if user passed reCAPTCHA # POST request to Google reCAPTCHA API url = 'https://www.google.com/recaptcha/api/siteverify?secret=%s&response=%s' % (GRECAPTCHA_SECRET, request.recaptcha) try: result = urlfetch.Fetch(url, method=2) except: raise endpoints.BadRequestException('urlfetch error: Unable to POST to Google reCAPTCHA') return status data = json.loads(result.content) if not data['success']: status.data = 'recaptcha_fail' return status user_id = 'ca_' + request.email # Get profile from datastore -- if profile not found, then profile=None profile_key = ndb.Key(Profile, user_id) profile = profile_key.get() # Check if profile exists. If not, return status if not profile: status.data = 'invalid_email' return status # If email unverified, return status if not profile.emailVerified: status.data = 'unverified_email' return status # Send password reset link to user's email if self._emailPwReset(profile): status.data = 'success' return status
def fbLogin(self, request): """ Handle Facebook login """ status = StringMsg() # return status message status.data = 'error' # default to error message, unless specified otherwise ''' # Swap short-lived token for long-lived token short_token = request.data url = 'https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id=%s&client_secret=%s&fb_exchange_token=%s' % ( FB_APP_ID, FB_APP_SECRET, short_token) try: result = urlfetch.Fetch(url, method=1) except: print('urlfetch error1') return status token = result.content.split('&')[0] # 'access_token=blahblahblah' ''' token = request.accessToken # Use token to get user info from API url = 'https://graph.facebook.com/v%s/me?access_token=%s&fields=name,id,email' % (FB_API_VERSION, token) try: result = urlfetch.Fetch(url, method=1) except: raise endpoints.BadRequestException('urlfetch error') return status data = json.loads(result.content) if 'error' in data: raise endpoints.BadRequestException('FB OAuth token error') return status user_id = 'fb_' + data['id'] first_name = data['name'].split()[0] last_name = data['name'].split()[-1] email = data['email'] # Get existing profile from datastore, or create new one profile_key = ndb.Key(Profile, user_id) profile = profile_key.get() # If profile already exists, return 'existing_user' # Unless, they have empty firstName (maybe they got d/c'ed on profile page) if profile: # If empty first name, return new_user if profile.firstName == '': status.data = 'new_user' return status # If user previously logged-out, update login status in NDB if not profile.loggedIn: profile.loggedIn = True profile.put() status.data = 'existing_user' return status # Else, create new profile and return 'new_user' profile = Profile( key = profile_key, userId = user_id, contactEmail = email, firstName = first_name, lastName = last_name, loggedIn = True, emailVerified = False, notifications = [True, False] ).put() status.data = 'new_user' return status