Skip to content
This repository was archived by the owner on Jan 21, 2026. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/src/main/java/com/indieweb/indigenous/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class User {
private String accountName;
private String externalId;
private String accessToken;
private String refreshToken;
private String microsubEndpoint;
private String micropubEndpoint;
private String micropubMediaEndpoint;
Expand Down Expand Up @@ -57,6 +58,14 @@ public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}

public String getRefreshToken() {
return refreshToken;
}

public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}

public String getExternalId() {
return externalId != null ? externalId : "";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ public List<User> getAllUsers() {
}

user.setAccessToken(token);
user.setRefreshToken(accountManager.getUserData(a, "refresh_token"));
user.setAccountType(INDIEWEB_ACCOUNT_TYPE);
user.setExternalId(accountManager.getUserData(a, "external_id"));
user.setAvatar(accountManager.getUserData(a, "author_avatar"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class AuthActivity extends AccountAuthenticatorActivity implements Volley
public final static String MASTODON_TOKEN_TYPE = "Mastodon";
public final static String PLEROMA_ACCOUNT_TYPE = "Pleroma";
public final static String PLEROMA_TOKEN_TYPE = "Pleroma";
final String ClientId = "https://indiepass.app/";
public final static String ClientId = "https://indiepass.app/";
final String RedirectUri = "https://indiepass.app/android-callback";
protected VolleyRequestListener volleyRequestListener;
String accountType = "indieweb";
Expand Down Expand Up @@ -534,13 +534,15 @@ private void validateIndieWebCode(final String code, final String returnedState)
public void onResponse(String response) {

String accessToken = "";
String refreshToken = "";
String errorMessage = "";
boolean accessTokenFound = false;

try {
JSONObject indieAuthResponse = new JSONObject(response);
accessToken = indieAuthResponse.getString("access_token");
accessTokenFound = true;
refreshToken = indieAuthResponse.optString("refresh_token");

// Check the profile key.
if (indieAuthResponse.has("profile")) {
Expand Down Expand Up @@ -619,6 +621,7 @@ public void onResponse(String response) {
am.setUserData(account, "micropub_media_endpoint", micropubMediaEndpoint);
am.setUserData(account, "author_name", authorName);
am.setUserData(account, "author_avatar", authorAvatar);
am.setUserData(account, "refresh_token", refreshToken);

// Set default account.
setDefaultAccount();
Expand All @@ -627,6 +630,7 @@ public void onResponse(String response) {
User user = new User();
user.setMicropubEndpoint(micropubEndpoint);
user.setAccessToken(accessToken);
user.setRefreshToken(refreshToken);
user.setAccount(account);
new MicropubAction(getApplicationContext(), user, layout).refreshConfig();

Expand Down
132 changes: 132 additions & 0 deletions app/src/main/java/com/indieweb/indigenous/util/HTTPRequest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.indieweb.indigenous.util;

import static com.indieweb.indigenous.users.AuthActivity.INDIEWEB_TOKEN_TYPE;

import android.accounts.AccountManager;
import android.content.Context;

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
Expand All @@ -9,9 +14,133 @@
import com.android.volley.toolbox.Volley;
import com.indieweb.indigenous.R;
import com.indieweb.indigenous.model.User;
import com.indieweb.indigenous.users.AuthActivity;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;

class TokenRefreshRequest extends StringRequest {
private final Context context;
private final User user;
private final Semaphore done;
private VolleyError failure;

TokenRefreshRequest(Context context, User user) {
super(Request.Method.POST, user.getTokenEndpoint(), null, null);
this.context = context;
this.user = user;
done = new Semaphore(0);
failure = null;
}

@Override
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("client_id", AuthActivity.ClientId);
params.put("grant_type", "refresh_token");
params.put("refresh_token", user.getRefreshToken());
return params;
}

@Override
public Map<String, String> getHeaders() {
HashMap<String, String> headers = new HashMap<>();
headers.put("Accept", "application/json");
return headers;
}

@Override
protected void deliverResponse(String response) {
try {
JSONObject refreshTokenResponse = new JSONObject(response);

AccountManager am = AccountManager.get(context);

String accessToken = refreshTokenResponse.optString("access_token");
if (!accessToken.isEmpty()) {
user.setAccessToken(accessToken);
am.setAuthToken(user.getAccount(), INDIEWEB_TOKEN_TYPE, accessToken);
}

String refreshToken = refreshTokenResponse.optString("refresh_token");
if (!refreshToken.isEmpty()) {
user.setRefreshToken(refreshToken);
am.setUserData(user.getAccount(), "refresh_token", refreshToken);
}

} catch (JSONException e) {
failure = new VolleyError(e);
}
done.release();
}

@Override
public void deliverError(VolleyError error) {
failure = error;
done.release();
}

public VolleyError await() throws InterruptedException {
done.acquire();
return failure;
}
}

class TokenRetryPolicy extends DefaultRetryPolicy {
private final Context context;
private final RequestQueue queue;
private final User user;
private boolean retried;

TokenRetryPolicy(Context context, RequestQueue queue, User user) {
this.context = context;
this.queue = queue;
this.user = user;
retried = false;
}

@Override
protected boolean hasAttemptRemaining() {
return !retried;
}

@Override
public void retry(VolleyError error) throws VolleyError {
if (!hasAttemptRemaining()) {
throw error;
}

String hdr = error.networkResponse.headers.get("WWW-Authenticate");
if (hdr.contains("invalid-token")) {
// Our access token has expired. Do we have a refresh token? If so, try to use it.

String refreshToken = user.getRefreshToken();
if (refreshToken != null && !refreshToken.isEmpty()) {
TokenRefreshRequest req = new TokenRefreshRequest(context, user);
queue.add(req);
VolleyError failure;
try {
failure = req.await();
} catch(InterruptedException e) {
failure = new VolleyError(e);
}
if (failure != null) {
throw failure;
}
retried = true;
return;
}

// We don't have a refresh token. For now, just fail.
// TODO: Restart the OAuth2 flow to get a new token.
}
throw error;
}
}

public class HTTPRequest {

Expand Down Expand Up @@ -67,6 +196,7 @@ public Map<String, String> getHeaders() {
};

RequestQueue queue = Volley.newRequestQueue(context);
request.setRetryPolicy(new TokenRetryPolicy(context, queue, user));
queue.add(request);
}

Expand Down Expand Up @@ -114,6 +244,7 @@ public Map<String, String> getHeaders() {
};

RequestQueue queue = Volley.newRequestQueue(context);
request.setRetryPolicy(new TokenRetryPolicy(context, queue, user));
queue.add(request);
}

Expand Down Expand Up @@ -161,6 +292,7 @@ public Map<String, String> getHeaders() {
};

RequestQueue queue = Volley.newRequestQueue(context);
request.setRetryPolicy(new TokenRetryPolicy(context, queue, user));
queue.add(request);
}
}