class Login(Resource): @spec( '/auth/login', 'User Login', body_params=[ ApiParam('email', 'user email', required=True, constraints=[LengthConstraint(5, 100)]), ApiParam('pwd', 'user password', required=True, constraints=[LengthConstraint(4)]), ], responses=[ ApiResponse(200, 'Login Succeed', token_example), ApiResponse(403, 'User Login Fail because email not signed up', {'message': '[email protected] is not signed up user.'}), ApiResponse(400, 'User Login Fail because email/pwd invalid', {'message': 'Password is invalid.'}), ApiResponse.error(406, 'Expired Temporary Password') ]) def post(self): args = get_args() email = args['email'].lower() pwd = args['pwd'] user = User(email, pwd=pwd) if not user.is_user(): err = email + ' is not signed up user.' raise InvalidUsage(err, status_code=403) is_tmp_pwd = user.check_tmp_pwd(pwd) if not user.check_pwd(pwd) and not is_tmp_pwd: err = 'Password is invalid.' raise InvalidUsage(err, status_code=400) if is_tmp_pwd and user.is_expired_tmp_pwd(): err = 'Expired Temporary Password' raise InvalidUsage(err, status_code=406) auth_token, refresh_token, exp_time = user.generate_auth_token() user.authenticated = True result = { 'auth_token': auth_token, 'refresh_token': refresh_token, 'exp_time': exp_time } if is_tmp_pwd: result['used_tmp_pwd'] = is_tmp_pwd return result
class FacebookSignup(Resource): @spec('/facebook/signup', 'New User Signup With Facebook', body_params=[ ApiParam('facebook_auth_token', 'auth token from facebook open sdk', required=True), ApiParam('email', 'user email', required=True), ApiParam('name', 'real name'), ApiParam('birthday', 'birthday', type='date'), ApiParam('gender', 'gender (male/female)', default='', constraints=[EnumConstraint(['', 'male', 'female'])]), ApiParam('place', 'office or home'), ApiParam('space', 'Where Arom will be installed'), ApiParam('purpose', 'Why use Arom'), ApiParam('prefer_scents', 'Prefer Scents', type='array', item=ApiParam("item", "item", type="string")) ], responses=[ ApiResponse(200, 'User Signup Succeed', dict(result=True)), ApiResponse( 500, 'User Signup Fail because of DB Issue', {'message': '[email protected] signup failed. Try again.'}), ApiResponse.error(403, 'Authorization Failed (from facebook server)'), ApiResponse.error(403, 'Already existing facebook user'), *signup_responses, ]) def post(self): args = get_args() fbauth_token = args['facebook_auth_token'] token_resp = FacebookApi.debug_token(fbauth_token) if 'error' in token_resp or not token_resp['data']['is_valid'] or \ token_resp['data']['app_id'] != app.config['FACEBOOK_APP_ID']: raise InvalidUsage("Authroization Failed", 403) facebook_id = str(token_resp['data']['user_id']) if UserModel.objects(facebook_id=facebook_id).first() is not None: raise InvalidUsage("Already existing facebook user", 403) signup(args, random_pw=True, validate_pw=False, facebook_id=facebook_id) return {'result': True}
class UserExists(Resource): @spec( '/user/exists', 'Check whether user exists or not', query_params=[ApiParam('email', 'email to find user', required=True)], responses=[ApiResponse(200, 'User presence', dict(exists=True))]) def get(self): args = get_args() user_model = UserModel.objects(email=args['email'].lower()).first() if user_model is None: return dict(exists=False) return dict(exists=True)
class Signup(Resource): @spec('/auth/signup', 'New User Signup', body_params=[ ApiParam('email', 'user email', required=True, constraints=[LengthConstraint(5, 100)]), ApiParam('pwd', 'user password', required=True, constraints=[LengthConstraint(4)]), ApiParam('name', 'real name', constraints=[LengthConstraint(1)]), ApiParam('birthday', 'birthday', type='date-time'), ApiParam('gender', 'gender (male/female)', constraints=[EnumConstraint(['male', 'female'])]), ApiParam('place', 'office or home'), ApiParam('space', 'Where Arom will be installed'), ApiParam('purpose', 'Why use Arom'), ApiParam('prefer_scents', 'Prefer Scents', type='array', item=ApiParam("item", "item", type="string")) ], responses=[ ApiResponse(200, 'User Signup Succeed', dict(result=True)), *signup_responses, ApiResponse( 500, 'User Signup Fail because of DB Issue', {'message': '[email protected] signup failed. Try again.'}) ]) def post(self): args = get_args() signup(args, random_pw=False, validate_pw=not app.config['TESTING']) return dict(result=True)
class RefreshAuthToken(Resource): @spec('/auth/refresh_token', 'Refresh User Auth Token and User Refresh Token', body_params=[ ApiParam('refresh_token', 'refresh token for user', required=True) ], responses=[ ApiResponse(200, 'Refresh Auth Token Success', token_example), ApiResponse(401, 'Refresh Auth Token Failed', dict(message='Refresh Token failed')) ]) def post(self): args = get_args() auth_token = request.headers.get('authorization') refresh_token = args['refresh_token'] if auth_token and refresh_token: auth_token = auth_token.encode() refresh_token = refresh_token.encode() try: user_id = jwt.decode(auth_token, app.config['SECRET_KEY'], algorithms='HS512', options=dict(verify_exp=False))['sub'] except jwt.InvalidTokenError: raise InvalidUsage('Auth Token is invalid.', status_code=401) try: user_db = UserModel.objects.get(user_id=user_id) except UserModel.DoesNotExist: raise InvalidUsage('This user does not exist.', status_code=401) if refresh_token == user_db.refresh_token.encode(): user = User(user_db.email, user_id=user_id) auth_token, refresh_token, exp_time = user.generate_auth_token( ) return { 'auth_token': auth_token, 'refresh_token': refresh_token, 'exp_time': exp_time } else: raise InvalidUsage('Refresh Token is invalid.', status_code=401) else: raise InvalidUsage('Token is not found.', status_code=401)
class FacebookLogin(Resource): @spec('/facebook/login', 'Facebook User Login', body_params=[ ApiParam('facebook_auth_token', 'auth token from facebook open sdk', required=True) ], responses=[ ApiResponse( 200, 'Login Succeed', { 'result': True, 'auth_token': 'Auth Token', 'refresh_token': 'Refresh Token', 'exp_time': '2017-12-31T23:59:59' }), ApiResponse( 404, 'User Not Found. Facebook token is valid but no user.', {'message': 'User Not Found'}), ApiResponse(403, 'Authorization Failed (from facebook server)', {'message': 'Authorization Failed'}), ]) def post(self): args = get_args() fbauth_token = args['facebook_auth_token'] resp = FacebookApi.debug_token(fbauth_token) if 'error' in resp or not resp['data']['is_valid'] or \ resp['data']['app_id'] != app.config['FACEBOOK_APP_ID']: raise InvalidUsage("Authroization Failed", 403) facebook_id = str(resp['data']['user_id']) try: user_model = UserModel.objects.get(facebook_id=facebook_id) except UserModel.DoesNotExist: raise InvalidUsage("User Not Found", 404) user = User() user.user_db = user_model auth_token, refresh_token, exp_time = user.generate_auth_token() user.authenticated = True return { 'result': True, 'auth_token': auth_token, 'refresh_token': refresh_token, 'exp_time': exp_time }
class KakaoLogin(Resource): @spec('/kakao/login', 'Kakao User Login', body_params=[ ApiParam('kakao_auth_token', 'auth token from kakao open sdk', required=True) ], responses=[ ApiResponse( 200, 'Login Succeed', { 'result': True, 'auth_token': 'Auth Token', 'refresh_token': 'Refresh Token', 'exp_time': '2017-12-31T23:59:59' }), ApiResponse(404, 'User Not Found. Kakao token is valid but no user.', {'message': 'User Not Found'}), ApiResponse(403, 'Authorization Failed (from kakao server)', {'message': 'Authorization Failed'}), ]) def post(self): args = get_args() kauth_token = args['kakao_auth_token'] resp = KakaoApi.access_token_info(kauth_token) if 'code' in resp: raise InvalidUsage("Authroization Failed", 403) kakao_id = str(resp['id']) try: user_model = UserModel.objects.get(kakao_id=kakao_id) except UserModel.DoesNotExist: raise InvalidUsage("User Not Found", 404) user = User() user.user_db = user_model auth_token, refresh_token, exp_time = user.generate_auth_token() user.authenticated = True return { 'result': True, 'auth_token': auth_token, 'refresh_token': refresh_token, 'exp_time': exp_time }
class ResetPassword(Resource): @spec('/auth/reset_password', 'Request reset password mail', body_params=[ ApiParam('email', 'email to reset password', required=True) ], responses=[ ApiResponse(200, 'Email sent', dict(result=True)), ApiResponse.error(404, 'User not found'), ApiResponse.error(500, 'Email Server Error') ]) def post(self): args = get_args() user_model = UserModel.objects(email=args['email']).first() if user_model is None: raise InvalidUsage('User not found', 404) tmp_password = gen_pwd() body = render_template("pwd_reset_mail.template.html", name=user_model.name, password=tmp_password) try: charset = 'UTF-8' response = apitools.email_client.send_email( Destination={'ToAddresses': [user_model.email]}, Message=dict( Subject=dict(Data="[아롬] 임시 비밀번호 발급", Charset='utf8'), Body=dict(Html=dict(Charset='utf8', Data=str(body)))), Source=app.config['CONTACT_EMAIL']) except Exception as ex: raise InvalidUsage("Email Server Error: {}".format(ex), 500) user_model.tmp_password = hash_pwd(tmp_password) user_model.tmp_password_valid_period = datetime.datetime.now() + \ app.config['PASSWORD_RESET_EXPIRE_DURATION'] user_model.save() return dict(result=True)
class DeviceRegister(Resource): @spec('/devices/<string:device_id>/register', 'Register Device ID in User DB Document', header_params=[*Swagger.Params.Authorization], path_params=[ApiParam('device_id', 'Device ID')], responses=[ ApiResponse(200, 'Register Device Succeed', shadow_example), ApiResponse( 401, 'Unauthenticated Device', dict(message='This user is not owner of this device.')), ApiResponse.error(406, 'Expired Temporary Code'), ApiResponse.error(409, "Already Registered Device") ]) @check_auth def post(self, device_id): user_db = g.user.user_db user_id = user_db.user_id res = iot_client.get_thing_shadow(thingName=device_id) payload = json.loads(res['payload'].read()) state = payload['state'] reported = state.get('reported', {}) desired = state.get('desired', {}) if device_id in user_db.devices: exceptionReport(g.user.user_db, get_path(), get_path_args(), get_args()) raise InvalidUsage('Already Registered Device', 409) if user_id == reported['owner_id']: user_db.update(**{'set__devices__' + device_id: device_id}) user_db.reload() return state else: exceptionReport(g.user.user_db, get_path(), get_path_args(), get_args()) raise InvalidUsage('This user is not owner of this device.', status_code=401)
class UserInfo(Resource): @spec('/auth/user_info', 'Get User Info', header_params=[*Swagger.Params.Authorization], query_params=[ApiParam('user_id', 'user_id to find user')], responses=[ ApiResponse(200, 'User Information', {'user_info': response_example}), ApiResponse(401, 'Get User Info Fail because of Auth Issue', {'message': 'Auth Token is not found.'}), ApiResponse(404, 'Get User Info Fail because User Does Not Exist', {'message': 'User does not exist.'}), ApiResponse(406, 'Get User Info Fail because of DB Issue', {'message': 'User Info cannot be found. Try Again.'}) ]) @check_auth def get(self): args = get_args() if args.get('user_id', None): try: user_db = UserModel.objects.get(user_id=args.get('user_id')) except UserModel.doesNotExist: raise InvalidUsage('User does not exist.', status_code=404) return dict( user_info=dict(name=user_db.name, picture=user_db.picture)) else: user = g.user.user_db return dict(user_info=user.marshall()) @spec('/auth/user_info', 'Update User Info', header_params=[*Swagger.Params.Authorization], body_params=[ ApiParam('pwd', 'user password', constraints=[LengthConstraint(4)]), ApiParam('name', 'real name', constraints=[LengthConstraint(1)]), ApiParam('birthday', 'birthday', type='date-time'), ApiParam('gender', 'gender (male/female)', default='', constraints=[EnumConstraint(['', 'male', 'female'])]), ApiParam('picture', 'profile image') ], responses=[ ApiResponse(200, 'User Information', {'user_info': response_example}), ApiResponse(401, 'Put User Info Fail because of Auth Issue', {'message': 'Auth Token is not found.'}), ApiResponse(406, 'Put User Info Fail because of DB Issue', {'message': 'User Info cannot be found. Try Again.'}) ]) @check_auth def put(self): args = get_args() user = g.user.user_db if args.get('pwd', None): user.password = hash_pwd(args.get('pwd')) if args.get('name', None): user.name = args.get('name') if args.get('birthday', None): user.birthday = arrow.get(args.get('birthday')).datetime if args.get('gender', None): user.gender = args.get('gender') if args.get('picture', None): user.picture = args.get('picture') user.save() return dict(user_info=user.marshall())
class AttachmentList(Resource): @spec('/attachments', 'Get My Attachment List', header_params=Swagger.Params.Authorization, query_params=Swagger.Params.Page, responses=[ ApiResponse( 200, "Succeed", dict(attachments=[attachment_example], limit=20, total_size=100)) ]) @check_auth def get(self): args = get_args() offset = args['offset'] limit = args['limit'] user_id = g.user.user_db.user_id query = ImageAttachmentModel.objects(user_id=user_id) attachments = query[offset:offset + limit] return dict( attachments=seq(attachments).map(lambda x: x.marshall()).list(), limit=limit, total_size=len(query)) @spec('/attachments', 'Post Image Attachment', header_params=Swagger.Params.Authorization, body_type="data", body_params=[ApiParam("image", type="file", required=True)], responses=[ ApiResponse(200, "Succeed", attachment_example), ApiResponse.error(400, "Invalid Image Type"), ApiResponse.error(500, "s3 upload error. try again") ]) @check_auth def post(self): args = get_args() image = args['image'] if not image.content_type.startswith('image'): exceptionReport(g.user.user_db, get_path(), get_path_args(), get_args()) raise InvalidUsage("Invalid Image Type", 400) extension = image.filename.split('.')[-1] image_model = ImageAttachmentModel(user_id=g.user.user_db.user_id, extension=extension, orignal_name=image.filename) image_model.save() key = image_model.s3filename upload_config = TransferConfig(use_threads=False) try: result = s3.upload_fileobj(image, app.config['ATTACHMENT_S3_BUCKET'], key, ExtraArgs={ "ACL": "public-read", "ContentType": image.content_type }, Config=upload_config) except: image_model.delete() exceptionReport(g.user.user_db, get_path(), get_path_args(), get_args()) raise InvalidUsage("s3 upload error. try again", 500) return image_model.marshall()
class KakaoSignup(Resource): @spec('/kakao/signup', 'New User Signup With Kakao', body_params=[ ApiParam('kakao_auth_token', 'auth token from kakao open sdk', required=True), ApiParam('email', 'user email', required=True), ApiParam('pwd', 'user password'), ApiParam('name', 'real name'), ApiParam('birthday', 'birthday', type='date'), ApiParam('gender', 'gender (male/female)', default='', constraints=[EnumConstraint(['', 'male', 'female'])]), ApiParam('place', 'office or home'), ApiParam('space', 'Where Arom will be installed'), ApiParam('purpose', 'Why use Arom'), ApiParam('prefer_scents', 'Prefer Scents', type='array', item=ApiParam("item", "item", type="string")) ], responses=[ ApiResponse(200, 'User Signup Succeed', dict(result=True)), ApiResponse( 500, 'User Signup Fail because of DB Issue', {'message': '[email protected] signup failed. Try again.'}), ApiResponse.error(403, 'Authorization Failed (from kakao server)'), ApiResponse.error(403, 'Already existing kakao user'), *signup_responses, ]) def post(self): args = get_args() kauth_token = args['kakao_auth_token'] resp = KakaoApi.user_info(kauth_token) if 'code' in resp: raise InvalidUsage("Authorization Failed", 403) kakao_id = str(resp['id']) properties = resp['properties'] profile_image = properties['profile_image'] nickname = properties['nickname'] thumbnail_image = properties['thumbnail_image'] if UserModel.objects(kakao_id=kakao_id).first() is not None: raise InvalidUsage("Already existing kakao user", 403) signup(args, random_pw=True, validate_pw=False, kakao_id=kakao_id) return {'result': True}
class DeviceState(Resource): @spec('/devices/<string:device_id>/state', 'Get Current State of Device', header_params=[*Swagger.Params.Authorization], path_params=[ApiParam('device_id', 'Device ID')], responses=[ ApiResponse(200, 'Register Device Succeed', shadow_example), ApiResponse( 401, 'Unauthenticated Device', dict(message='This user is not owner of this device.')) ]) @check_device def get(self, device_id): payload = json.dumps( {'state': { 'desired': { 'timestamp': int(time.time() * 1000) } }}) res = iot_client.get_thing_shadow(thingName=device_id) iot_client.update_thing_shadow(thingName=device_id, payload=payload) payload = json.loads(res['payload'].read()) state = payload['state'] state['name'] = g.user.user_db.devices[device_id] return state @spec('/devices/<string:device_id>/state', 'Update Current State of Device', header_params=[*Swagger.Params.Authorization], path_params=[ApiParam('device_id', 'Device ID')], body_name="Device States", body_params=[ ApiParam('state', 'Device State Info', "object", properties=[ ApiParam("state1", type="boolean"), ApiParam("state2", type="number"), ApiParam("state3", type="string") ]) ], responses=[ ApiResponse(200, 'Register Device Succeed', shadow_example), ApiResponse( 401, 'Unauthenticated Device', dict(message='This user is not owner of this device.')) ]) @check_device def post(self, device_id): args = get_args() desired = args['state'] desired['timestamp'] = int(time.time() * 1000) if 'name' in desired: name = desired.pop('name') g.user.user_db.devices[device_id] = name g.user.user_db.save() payload = json.dumps({'state': {'desired': desired}}) iot_client.update_thing_shadow(thingName=device_id, payload=payload) res = iot_client.get_thing_shadow(thingName=device_id) data = json.loads(res['payload'].read()) state = data['state'] state['name'] = g.user.user_db.devices[device_id] return state