from urllib.parse import urlparse from secrets import token_urlsafe from flask import Flask, render_template, url_for, redirect, request, session from authlib.integrations.flask_client import OAuth from requests import post app = Flask(__name__) SECRET_KEY = token_urlsafe(32) app.secret_key = SECRET_KEY oauth = OAuth(app) # Required for using authlib to process OAuth. # Many fields are blank or wrong, but that's because we'll fill them later. gotosocial = oauth.register( name='gotosocial', client_id='', client_secret='', access_token_url='/oauth/token', access_token_params={'response_type': 'token', 'grant_type': 'authorization_code', 'client_id': '', 'client_secret': ''}, authorize_url='/oauth/authorize', authorize_params={'grant_type': 'authorization_code'}, api_base_url='/api', client_kwargs={'scope': 'read'}, ) @app.route('/') def index(): return render_template('index.html') # Initialization of OAuth flow. # Once we know what domain the instance is at, we need to do # an API call to register the app and get a client_id and client_secret. @app.route('/set_domain', methods=['POST']) def set_domain(): if (request.method == 'POST'): try: # First, let's parse the domain URL and clean up if necessary domain_parse = urlparse(request.form["domain"]) if (domain_parse): if (domain_parse.scheme): domain = domain_parse.geturl() else: domain = f'https://{domain_parse.geturl()}' # We need to construct our request and send it to the instance. # This is done with the Requests library, since authlib doesn't # provide a method to handle that. payload = { 'client_name': 'gotosocial-fe', 'redirect_uris': url_for('index', _external=True) } response = post(f'{domain}/api/v1/apps', data=payload) client_data = response.json() # With any luck, we'll have the client id and secret. # This sets up the OAuth object we registered earlier with # The correct parameters from the data we recieved. oauth.gotosocial.client_id = client_data['client_id'] oauth.gotosocial.client_secret = client_data['client_secret'] oauth.gotosocial.access_token_params = { 'response_type': 'token', 'grant_type': 'authorization_code', 'client_id': client_data['client_id'], 'client_secret': client_data['client_secret'] } oauth.gotosocial.access_token_url = f'{domain}{oauth.gotosocial.access_token_url}' oauth.gotosocial.authorize_url = f'{domain}{oauth.gotosocial.authorize_url}' oauth.gotosocial.api_base_url = f'{domain}{oauth.gotosocial.api_base_url}' # Now we need to redirect the user to their instance # for authentication. return redirect('/login') else: # If we don't get a url that's parseable. return "Did you even submit anything?" except: # If something bad happens in the app registration with # the domain, assume it's not a valid instance domain. return "Are you sure you're putting in a GoToSocial instance url?" else: return "You can't do a GET request here." # Now we send the user to their domain so they can authenticate on # our behalf and give us permission to use their account with the # specified scope(s). @app.route('/login') def login(): redirect_uri = url_for('authorize', _external=True) return oauth.gotosocial.authorize_redirect(redirect_uri) # Once we're back, do a quick credentials verification to make sure # we have a valid access token. @app.route('/authorize') def authorize(): token = oauth.gotosocial.authorize_access_token() session['oauth_token'] = token response = oauth.gotosocial.get( 'api/v1/accounts/verify_credentials') response.raise_for_status() return redirect(url_for('home', _external=True)) # If the flow succeeded, the user should get dropped onto this # page, with a very basic home timeline displayed using the API # call we should be able to do with our access token. @app.route('/home') def home(): # TODO: Long-term shoukd make sure we store the token in # localStorage or sessionStorage and try to retrieve it # from there first. token = session['oauth_token'] response = oauth.gotosocial.get( 'api/v1/timelines/home', token=token) response.raise_for_status() home_timeline = response.json() return render_template(f'{url_for("home")}/index.html', home_timeline=home_timeline)