fix: make access tokens non-expiring (matching Mastodon behavior)

Access tokens were expiring after 1 hour, causing Phanpy/Elk/Moshidon
to get 401 errors and become unusable. Mastodon itself creates
non-expiring access tokens (valid until revoked).

Removed expiresAt from all three grant types (authorization_code,
client_credentials, refresh_token). Refresh tokens keep their 90-day
expiry as a safety measure. Removed expires_in from JSON responses.
This commit is contained in:
Ricardo
2026-03-31 14:53:29 +02:00
parent 32bf01e7bb
commit 5f023e4d26
2 changed files with 5 additions and 9 deletions
+4 -8
View File
@@ -467,7 +467,6 @@ router.post("/oauth/token", async (req, res, next) => {
accessToken, accessToken,
createdAt: new Date(), createdAt: new Date(),
grantType: "client_credentials", grantType: "client_credentials",
expiresAt: new Date(Date.now() + 3600 * 1000),
}); });
return res.json({ return res.json({
@@ -475,7 +474,6 @@ router.post("/oauth/token", async (req, res, next) => {
token_type: "Bearer", token_type: "Bearer",
scope: "read", scope: "read",
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
expires_in: 3600,
}); });
} }
@@ -510,9 +508,9 @@ router.post("/oauth/token", async (req, res, next) => {
$set: { $set: {
accessToken: newAccessToken, accessToken: newAccessToken,
refreshToken: newRefreshToken, refreshToken: newRefreshToken,
expiresAt: new Date(Date.now() + 3600 * 1000),
refreshExpiresAt: new Date(Date.now() + 90 * 24 * 3600 * 1000), refreshExpiresAt: new Date(Date.now() + 90 * 24 * 3600 * 1000),
}, },
$unset: { expiresAt: "" },
}, },
); );
@@ -522,7 +520,6 @@ router.post("/oauth/token", async (req, res, next) => {
scope: existing.scopes.join(" "), scope: existing.scopes.join(" "),
created_at: Math.floor(existing.createdAt.getTime() / 1000), created_at: Math.floor(existing.createdAt.getTime() / 1000),
refresh_token: newRefreshToken, refresh_token: newRefreshToken,
expires_in: 3600,
}); });
} }
@@ -590,8 +587,9 @@ router.post("/oauth/token", async (req, res, next) => {
} }
} }
// Generate access token and refresh token with expiry. // Generate access token and refresh token.
const ACCESS_TOKEN_TTL = 3600 * 1000; // 1 hour // Access tokens do not expire (matching Mastodon behavior — valid until revoked).
// Refresh tokens expire after 90 days as a safety measure.
const REFRESH_TOKEN_TTL = 90 * 24 * 3600 * 1000; // 90 days const REFRESH_TOKEN_TTL = 90 * 24 * 3600 * 1000; // 90 days
const accessToken = randomHex(64); const accessToken = randomHex(64);
const refreshToken = randomHex(64); const refreshToken = randomHex(64);
@@ -601,7 +599,6 @@ router.post("/oauth/token", async (req, res, next) => {
$set: { $set: {
accessToken, accessToken,
refreshToken, refreshToken,
expiresAt: new Date(Date.now() + ACCESS_TOKEN_TTL),
refreshExpiresAt: new Date(Date.now() + REFRESH_TOKEN_TTL), refreshExpiresAt: new Date(Date.now() + REFRESH_TOKEN_TTL),
}, },
}, },
@@ -613,7 +610,6 @@ router.post("/oauth/token", async (req, res, next) => {
scope: grant.scopes.join(" "), scope: grant.scopes.join(" "),
created_at: Math.floor(grant.createdAt.getTime() / 1000), created_at: Math.floor(grant.createdAt.getTime() / 1000),
refresh_token: refreshToken, refresh_token: refreshToken,
expires_in: 3600,
}); });
} catch (error) { } catch (error) {
next(error); next(error);
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@rmdes/indiekit-endpoint-activitypub", "name": "@rmdes/indiekit-endpoint-activitypub",
"version": "3.12.3", "version": "3.12.4",
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.", "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
"keywords": [ "keywords": [
"indiekit", "indiekit",