Check chrome web store user license from google app engine - google-app-engine

Is it possible to use client-side generated access token in a server-side call to chromewebstore/v1.1/userlicenses/ to check user license? Both extension and app engine project registered on the same gmail account. I want to be able to tell if the user of my webapp has purchased my extension.
gapi.auth.authorize({
scope: [
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/plus.login",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/chromewebstore.readonly"].join(" "),
client_id: "xxxxx"
}, () => gapi.client.myapi.check_payment().execute())
App engine code
import os
import urllib
import endpoints
import httplib2
from oauth2client import client
from protorpc import remote
from protorpc.message_types import VoidMessage
EXTENSION_ID = "xxxxx" # my extension id from Chrome Web Store Developer Dashboard
API_KEY = "xxxxx" # api key from Google APIs Console
CLIENT_ID = "xxxxx" # OAuth 2.0 client ID from Google APIs Console
SCOPES = [endpoints.EMAIL_SCOPE]
#endpoints.api(name="myapi", version="v1", allowed_client_ids=[CLIENT_ID], scopes=SCOPES)
class MyApi(remote.Service):
#endpoints.method(VoidMessage, VoidMessage)
def check_payment(self, msg):
user = endpoints.get_current_user()
assert user is not None
if "HTTP_AUTHORIZATION" in os.environ:
(tokentype, token) = os.environ["HTTP_AUTHORIZATION"].split(" ")
credentials = client.AccessTokenCredentials(token, 'my-user-agent/1.0')
http = credentials.authorize(httplib2.Http())
params = urllib.urlencode({"key": API_KEY})
url = "https://www.googleapis.com/chromewebstore/v1.1/userlicenses/%s?%s" % (EXTENSION_ID, params)
response = http.request(url)
Responds with 403 status: {"domain":"global","reason":"forbidden","message":"You don\'t have access to licensing data for App ID: xxxxx"}

So yeah, there is no way for that to work, this kind of a request can only by authorized with a token minted by identity.getAuthToken.

Related

Flask and React CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request

I am trying to connect my react frontend to my flask api backend. Note that flask-cors is already installed.
I initiated CORS as CORS(app) . the login passes but I keep getting this error when I go to project route:
Access to fetch at 'http://192.168.101.4:5000/project' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
My init.py file:
from flask import Flask, request, make_response
from .extensions import db, migrate, jwt, mail, CORS
from .models import TokenBlocklist
from .routes.base import base
from .routes.auth import auth
from .routes.core import core
from .routes.admin import admin
from .routes.user import user
from .routes.project import project
def create_app(config_file='config.py'):
app = Flask(__name__)
app.config.from_pyfile(config_file)
#jwt.token_in_blocklist_loader
def check_if_token_revoked(jtw_header, jwt_payload: dict)->bool:
jti = jwt_payload['jti']
token = db.session.query(TokenBlocklist.id).filter_by(jti=jti).scalar()
return token is not None
# initiate
db.init_app(app)
migrate.init_app(app, db)
jwt.init_app(app)
mail.init_app(app)
CORS(app)
app.register_blueprint(base)
app.register_blueprint(auth, url_prefix='/auth')
app.register_blueprint(core, url_prefix='/core')
app.register_blueprint(admin, url_prefix='/admin')
app.register_blueprint(user, url_prefix='/user')
app.register_blueprint(project, url_prefix='/project')
return app
my auth.py:
"""
Route: endpoints for authentication
"""
from flask import Blueprint, jsonify, request, current_app, url_for, make_response
import datetime
import uuid
import validators
from itsdangerous import URLSafeTimedSerializer
from flask_jwt_extended import create_access_token, create_refresh_token, get_jwt_identity, jwt_required, unset_jwt_cookies, get_jwt, set_access_cookies, set_refresh_cookies
from werkzeug.security import generate_password_hash
from flask_cors import CORS
from ..utils import send_mail
from ..extensions import db, status, jwt
from ..models import User, TokenBlocklist
auth = Blueprint('auth', __name__)
CORS(auth)
#auth.route('/confirm-email/<token>')
def confirm_email(token):
s = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
try:
email = s.loads(token, salt='email-confirm', max_age=200)
# mark user mail as confirmed
user = User.query.filter_by(email=email).first()
if not user.is_confirmed:
user.is_confirmed = True
db.session.commit()
return jsonify(msg='email confirmed'), status.ok
except:
return jsonify(msg = False), status.bad
#auth.route('/register', methods=['POST'])
def register():
"""
recieve request->username, email, password
check username & email exists or not, if not hash password and store.
send email with confirmation link
"""
data = request.get_json()
username = data['username']
email = data['email']
password = data['password']
# check existance of requirements in request json
if not username or not email or not password:
return jsonify(msg='missing json data'), status.bad
# email validations
if not validators.email(email):
return jsonify(msg='invalid email address'),
# check if username and email is taken
user = User.query.filter_by(username=username).first()
if user:
return jsonify(msg='username already taken')
user = User.query.filter_by(email=email).first()
if user:
return jsonify(msg='email already taken')
# save new user in db
new_user = User(
username = username,
email = email,
password = generate_password_hash(password, 'sha256'), # probably not a good practice to use User
public_id = uuid.uuid4()
)
try:
db.session.add(new_user)
db.session.commit()
except:
return jsonify(msg='could not save')
# create email confirmation link
token = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
token = token.dumps(email, salt='email-confirm')
link = url_for('auth.confirm_email', token=token, _external=True)
# send mail
body = f'confirmation link: {link}'
mail_sent = send_mail(recipient=email, body=body, subject='Linkage Email Confirmation')
if not mail_sent:
return jsonify(msg= 'email could not be sent')
return jsonify(msg= 'mail sent' ,email=email, token=token), 201
#auth.route('/login', methods=['POST'])
def login():
"""
recieve json data->email, password
check if email exists,
check user's hashed password and if email is confirmed.
generate refresh and access token and return
"""
data = request.get_json()
email = data['email']
password = data['password']
user = User.query.filter_by(email=email).first()
# email not found
if not user:
return make_response(
'User not found',
401,
{'WWW-Authenticate' : 'Basic realm ="could not verify"'}
)
# email found
verified = user.verify_password(password)
if verified is False:
return make_response(
'password mismatch',
401,
{'WWW-Authenticate' : 'Basic realm ="could not verify"'}
)
# authenticated, now generate tokens
user_public_id = user.public_id
refresh_token = create_refresh_token(identity=user_public_id)
access_token = create_access_token(identity=user_public_id)
# i am totally not sure about the following, but the fe dev *insists*
response = jsonify({"x-access-token": access_token})
response.set_cookie("refresh_token_cookie", refresh_token)
return response, 200
And Finally my project.py:
from datetime import datetime
from flask import Blueprint, jsonify, request, make_response
from flask_jwt_extended import get_jwt_identity, jwt_required
from ..extensions import db
from ..models import Project
project = Blueprint('project', __name__)
# this route must be deleted before production
#project.route('/', methods=['GET'])
#jwt_required()
def all_projects():
user_public_id = get_jwt_identity()
projects = Project.query.filter_by(user_public_id=user_public_id).all()
projects_data = []
for project in projects:
project_data = {}
project_data['id'] = project.id
project_data['domain'] = project.domain
project_data['name'] = project.name
project_data['user_public_id'] = project.user_public_id
project_data['wp_password'] = project.wp_password
project_data['wp_username'] = project.wp_username
project_data['date_added'] = project.date_added
projects_data.append(project_data)
return jsonify(projects = projects_data), 20
CORS related configs in my config.py file:
#CORS
CORS_ALLOW_HEADERS = ['Content-Type', 'Authorization']
CORS_SUPPORTS_CREDENTIALS = True
CORS_HEADERS = ['Content-Type', 'Authorization']
I am totally lost here. I don't know where is the redirect is happening.
Thank you for your time.

Why does my Flask React.js session not work?

I'm making the login for a app in react with the backend in flask but the session is not working.
The flask app is running in a remote server on pythonanywhere and I'm testing the react app on my localhost:3000
Here's the code for the flask app:
app = Flask(__name__)
app.config["SESSION_PERMANENT"] = True
app.config["SESSION_TYPE"] = "filesystem"
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=5)
Session(app)
CORS(app, supports_credentials = True, resources={r"/webapp/*": {"origins": "http://localhost:3000/"}})
#app.route("/webapp/login", methods = ["POST"])
def login():
user = request.get_json(force=True)
Email = user.get("email", "")
Password = user.get("password", "")
login = Login.login(Email, Password)
if login["Success"]:
session["email"] = Email
return login
#app.route("/webapp/checksession", methods = ["GET"])
def checksession():
sessionEmail = session.get("email", "")
if sessionEmail == "":
return utils.returnResult(False, "Session not valid")
return utils.returnResult(True, "")
And in the React app I use axios to do the login and check the session in the server, for example:
axios.get(APIURL + CHECK_SESSION, {withCredentials: true})
.then(response => {
console.log(response.headers);
setSession(response.data.success)
setCheckedSession(true)
})
.catch(err => {
setSession(false)
setCheckedSession(true)
if(err.response) {
}
else {
window.alert('Could not establish a connection to the server!');
}
});
In the dev tools we can see that the server is sending the cookie session:
But when I check the cookies for the app there's nothing there:
(Sorry for the Portuguese)
And in the axios promise handler, when I print the headers of the response, this is all I get:
So every time the react app checks if the user has a valid session in the server, the server creates a new session, which means that the react app is not saving and sending the cookie to the server.
Also, when I test this with postman everything works fine.
I searched all over the places and I can't find an answer for this. Can anyone help me figure out what I'm doing wrong, please?
Turns out it was missing configurations in the flask session:
app.config["SESSION_COOKIE_SAMESITE"] = "None"
app.config["SESSION_COOKIE_SECURE"] = True

Django DRF + Allauth: OAuth2Error: Error retrieving access token on production build

We are integrating DRF (dj_rest_auth) and allauth with the frontend application based on React. Recently, the social login was added to handle login through LinkedIn, Facebook, Google and GitHub. Everything was working good on localhost with each of the providers. After the staging deployment, I updated the secrets and social applications for a new domain. Generating the URL for social login works fine, the user gets redirected to the provider login page and allowed access to login to our application, but after being redirected back to the frontend page responsible for logging in - it results in an error: (example for LinkedIn, happens for all of the providers)
allauth.socialaccount.providers.oauth2.client.OAuth2Error:
Error retrieving access token:
b'{"error":"invalid_redirect_uri","error_description":"Unable to retrieve access token: appid/redirect uri/code verifier does not match authorization code. Or authorization code expired. Or external member binding exists"}'
Our flow is:
go to frontend page -> click on provider's icon ->
redirect to {BACKEND_URL}/rest-auth/linkedin/url/ to make it a POST request (user submits the form) ->
login on provider's page ->
go back to our frontend page {frontend}/social-auth?source=linkedin&code={the code we are sending to rest-auth/$provider$ endpoint}&state={state}->
confirm the code & show the profile completion page
The adapter definition (same for every provider):
class LinkedInLogin(SocialLoginView):
adapter_class = LinkedInOAuth2Adapter
client_class = OAuth2Client
#property
def callback_url(self):
return self.request.build_absolute_uri(reverse('linkedin_oauth2_callback'))
Callback definition:
def linkedin_callback(request):
params = urllib.parse.urlencode(request.GET)
return redirect(f'{settings.HTTP_PROTOCOL}://{settings.FRONTEND_HOST}/social-auth?source=linkedin&{params}')
URLs:
path('rest-auth/linkedin/', LinkedInLogin.as_view(), name='linkedin_oauth2_callback'),
path('rest-auth/linkedin/callback/', linkedin_callback, name='linkedin_oauth2_callback'),
path('rest-auth/linkedin/url/', linkedin_views.oauth2_login),
Frontend call to send the access_token/code:
const handleSocialLogin = () => {
postSocialAuth({
code: decodeURIComponent(codeOrAccessToken),
provider: provider
}).then(response => {
if (!response.error) return history.push(`/complete-profile?source=${provider}`);
NotificationManager.error(
`There was an error while trying to log you in via ${provider}`,
"Error",
3000
);
return history.push("/login");
}).catch(_error => {
NotificationManager.error(
`There was an error while trying to log you in via ${provider}`,
"Error",
3000
);
return history.push("/login");
});
}
Mutation:
const postSocialUserAuth = builder => builder.mutation({
query: (data) => {
const payload = {
code: data?.code,
};
return {
url: `${API_BASE_URL}/rest-auth/${data?.provider}/`,
method: 'POST',
body: payload,
}
}
Callback URLs and client credentials are set for the staging environment both in our admin panel (Django) and provider's panel (i.e. developers.linkedin.com)
Again - everything from this setup is working ok in the local environment.
IMPORTANT
We are using two different domains for the backend and frontend - frontend has a different domain than a backend
The solution was to completely change the callback URL generation
For anyone looking for a solution in the future:
class LinkedInLogin(SocialLoginView):
adapter_class = CustomAdapterLinkedin
client_class = OAuth2Client
#property
def callback_url(self):
callback_url = reverse('linkedin_oauth2_callback')
site = Site.objects.get_current()
return f"{settings.HTTP_PROTOCOL}://{site}{callback_url}"
Custom adapter:
class CustomAdapterLinkedin(LinkedInOAuth2Adapter):
def get_callback_url(self, request, app):
callback_url = reverse(provider_id + "_callback")
site = Site.objects.get_current()
return f"{settings.HTTP_PROTOCOL}://{site}{callback_url}"
It is important to change your routes therefore for URL generation:
path('rest-auth/linkedin/url/', OAuth2LoginView.adapter_view(CustomAdapterLinkedin))
I am leaving this open since I think this is not expected behaviour.

Frontend Authenticated request Google Cloud Storage

I am using a Google Cloud Storage bucket to upload some of my users files. I do not want them to appear as public, so I created a service account representing my frontend app.
I want to know how to make Authenticated Request to Google Cloud Storage, without using the #google-cloud/storage npm package from my frontend app.
I know I need to include Auhtorization: Bearer <token> in my request headers but, how do I get this token ?
I'm using React on my frontend app.
Google has a number of libraries that you can use. Here is one example:
var { google } = require('googleapis')
const request = require('request')
// The service account JSON key file to use to create the Access Token
let privatekey = require('/path/service-account.json')
let scopes = 'https://www.googleapis.com/auth/devstorage.read_only'
let jwtClient = new google.auth.JWT(
privatekey.client_email,
null,
privatekey.private_key,
scopes
)
jwtClient.authorize(function(err, _token) {
if (err) {
console.log(err)
return err
} else {
console.log('token obj:', _token)
console.log('access token:', _token.access_token)
headers: {
"Authorization": "Bearer " + _token.access_token
}
// Make requests to Cloud Storage here
}
})

AngularJS + django REST api

I'm attempting to build an api with DRF.
Client is a cordova app backed with AngularJS.
When I try to post some user object using $resource I'm getting a 403 forbidden response from django.
Below is some code which I think is relevant for the issue:
The API Call:
$rootScope.user =
User.get({id: response.id}).$promise.then(angular.noop, function (e) {
if (e.status == 404) { //If not found, register the user.
$rootScope.user = new User();
Object.keys(response).forEach(function (key) {
$rootScope.user[key] = response[key];
});
$rootScope.user.$save(); //Fails here! 403.
}
else
console.log(JSON.stringify(e.msg));
});
The User factory:
.factory('User', function ($resource, serverConstants) {
return $resource(serverConstants.serverUrl + '/users/:id');
})
django view:
# Users
class UserSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.CharField(max_length=100,required=True)
email = serializers.EmailField(required=False,allow_blank=True)
joined = serializers.DateField(required=False,default=datetime.date.today)
class Meta:
model = models.User
fields = ('joined', 'id', 'email')
def get_validation_exclusions(self):
exclusions = super(UserSerializer, self).get_validation_exclusions()
return exclusions + ['owner']
class UserViewSet(viewsets.ModelViewSet):
queryset = models.User.objects.all()
serializer_class = UserSerializer
PS: I've configured angular to use CSRF cookie and django to allow CORS
Thanks in advance!
Your /user/:id endpoint requires authenticated requests.
You need to authenticate your client's requests using one of the methods specified on the previous link.
Given your app runs in a WebView and then has a builtin cookies handling, SessionAuthentication is the more straightforward to implement.
If you want the endpoint to not require authentication, you can set its permission_classes attribute like so:
from rest_framework.permissions import AllowAny
class UserViewSet(viewsets.ModelViewSet):
queryset = models.User.objects.all()
serializer_class = UserSerializer
permission_classes = (AllowAny, )
I guess with DRF you mean the django-rest-framework.
If yes, have a look here:
http://www.django-rest-framework.org/api-guide/authentication/
You can make the view public but using AllowAny.
from rest_framework.permissions import AllowAny
from rest_framework import generics
restapi_permission_classes = (AllowAny,)
class MyListView(generics.ListCreateAPIView):
serializer_class = MyObjectSerializer
permission_classes = restapi_permission_classes
queryset = MyObject.objects.all()
However I'd recommend you to use proper authentication once you are done with testing. I've been using the token authentication.
Have a look at this post for more details:
Django Rest Framework Token Authentication

Resources