openvibers/openvibe.live Commits
Restream OAuth: a redirect mismatch says which URL to add to the platform's app (the owner saw only Twitch's 'redirect_uri does not match') (#162)
Changes
@@@ -642,6 +642,19 @@ router.get('/oauth/:platform/start', requireAuth, async (req, res) => {}});++/**++ * What a provider's refusal means for the person connecting. A redirect mismatch (Twitch "redirect_mismatch", or a++ * description saying the redirect URI does not match) is the developer app's redirect list, not anything they did:++ * the owner tried to connect Twitch four times on 10-16 and saw only "The provided redirect_uri does not match".++ */++function oauthErrorText(platform, err, description) {++ const name = (platformOAuth.PLATFORMS[platform] && platformOAuth.PLATFORMS[platform].name) || String(platform).charAt(0).toUpperCase() + String(platform).slice(1);++ if (/redirect/i.test(String(err || '')) || /redirect[_ ]?ur[il]/i.test(String(description || ''))) {++ return `${name} refused the sign-in because this site's address is not on its app's list. In the ${name} developer console, add ${platformOAuth.redirectUri(platform)} as an OAuth redirect URL for the app whose client ID is set in Admin → Integrations, then connect again. Until then, paste the stream key into the destination by hand.`;++ }++ return description || err;++}++// ── GET /oauth/:platform/callback — exchange code, save connection + destinationrouter.get('/oauth/:platform/callback', async (req, res) => {const { platform } = req.params;@@@ -652,7 +665,7 @@ router.get('/oauth/:platform/callback', async (req, res) => {const { code, state, error: oauthErr, error_description } = req.query;if (oauthErr) {console.warn(`[Restream OAuth] ${platform} provider error: ${oauthErr} — ${error_description || ''} | our redirect_uri=${platformOAuth.redirectUri(platform)}`);-- return send({ ok: false, platform, error: error_description || oauthErr });++ return send({ ok: false, platform, error: oauthErrorText(platform, oauthErr, error_description) });}if (!code || !state) return send({ ok: false, platform, error: 'Missing authorization code' });@@@ -749,3 +762,4 @@ router.delete('/oauth/:platform/connection', requireAuth, async (req, res) => {});module.exports = router;++module.exports.oauthErrorText = oauthErrorText;nnew file mode 100644@@@ -0,0 +1,22 @@++/**++ * A provider that refuses a restream sign-in says what to do (server/streaming/restream-routes.js). Twitch answers++ * "redirect_mismatch — The provided redirect_uri does not match" when its developer app does not list this site's++ * callback; the owner tried to connect Twitch four times on 2026-10-16 and saw only that sentence. The page now names++ * the exact URL to add and the hand-pasted key as the way meanwhile. Other errors pass through as the provider wrote++ * them.++ *++ * node test/restream-oauth-errors.test.js++ */++'use strict';++const assert = require('assert');++const { oauthErrorText } = require('../server/streaming/restream-routes');++const { redirectUri } = require('../server/integrations/platform-oauth');++++const twitch = oauthErrorText('twitch', 'redirect_mismatch', 'The provided redirect_uri does not match');++assert.ok(twitch.startsWith('Twitch refused the sign-in because this site\'s address is not on its app\'s list.'), twitch);++assert.ok(twitch.includes(redirectUri('twitch')) && /\/api\/restream\/oauth\/twitch\/callback/.test(twitch), 'names the exact URL to add');++assert.ok(/paste the stream key into the destination by hand/.test(twitch), 'and the way meanwhile');++assert.ok(/YouTube refused the sign-in/.test(oauthErrorText('youtube', 'invalid_request', 'Invalid parameter value for redirect_uri: Mismatch')), 'a description about the redirect URI counts too');++assert.strictEqual(oauthErrorText('kick', 'access_denied', 'The user denied the request'), 'The user denied the request', 'other errors pass through');++assert.strictEqual(oauthErrorText('kick', 'access_denied', ''), 'access_denied');++console.log('restream oauth errors: all checks passed');
@@ -642,6 +642,19 @@ router.get('/oauth/:platform/start', requireAuth, async (req, res) => {
@@ -642,6 +642,19 @@ router.get('/oauth/:platform/start', requireAuth, async (req, res) => {
}
}
});
});
+/**
+ * What a provider's refusal means for the person connecting. A redirect mismatch (Twitch "redirect_mismatch", or a
+ * description saying the redirect URI does not match) is the developer app's redirect list, not anything they did:
+ * the owner tried to connect Twitch four times on 10-16 and saw only "The provided redirect_uri does not match".
+ */
+function oauthErrorText(platform, err, description) {
+ const name = (platformOAuth.PLATFORMS[platform] && platformOAuth.PLATFORMS[platform].name) || String(platform).charAt(0).toUpperCase() + String(platform).slice(1);
+ if (/redirect/i.test(String(err || '')) || /redirect[_ ]?ur[il]/i.test(String(description || ''))) {
+ return `${name} refused the sign-in because this site's address is not on its app's list. In the ${name} developer console, add ${platformOAuth.redirectUri(platform)} as an OAuth redirect URL for the app whose client ID is set in Admin → Integrations, then connect again. Until then, paste the stream key into the destination by hand.`;
+ }
+ return description || err;
+}
+
// ── GET /oauth/:platform/callback — exchange code, save connection + destination
// ── GET /oauth/:platform/callback — exchange code, save connection + destination
router.get('/oauth/:platform/callback', async (req, res) => {
router.get('/oauth/:platform/callback', async (req, res) => {
const { platform } = req.params;
const { platform } = req.params;
@@ -652,7 +665,7 @@ router.get('/oauth/:platform/callback', async (req, res) => {
@@ -652,7 +665,7 @@ router.get('/oauth/:platform/callback', async (req, res) => {
const { code, state, error: oauthErr, error_description } = req.query;
const { code, state, error: oauthErr, error_description } = req.query;
if (oauthErr) {
if (oauthErr) {
console.warn(`[Restream OAuth] ${platform} provider error: ${oauthErr} — ${error_description || ''} | our redirect_uri=${platformOAuth.redirectUri(platform)}`);
console.warn(`[Restream OAuth] ${platform} provider error: ${oauthErr} — ${error_description || ''} | our redirect_uri=${platformOAuth.redirectUri(platform)}`);
- return send({ ok: false, platform, error: error_description || oauthErr });
+ return send({ ok: false, platform, error: oauthErrorText(platform, oauthErr, error_description) });
}
}
if (!code || !state) return send({ ok: false, platform, error: 'Missing authorization code' });
if (!code || !state) return send({ ok: false, platform, error: 'Missing authorization code' });
@@ -749,3 +762,4 @@ router.delete('/oauth/:platform/connection', requireAuth, async (req, res) => {
@@ -749,3 +762,4 @@ router.delete('/oauth/:platform/connection', requireAuth, async (req, res) => {
});
});
module.exports = router;
module.exports = router;
+module.exports.oauthErrorText = oauthErrorText;
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+/**
+ * A provider that refuses a restream sign-in says what to do (server/streaming/restream-routes.js). Twitch answers
+ * "redirect_mismatch — The provided redirect_uri does not match" when its developer app does not list this site's
+ * callback; the owner tried to connect Twitch four times on 2026-10-16 and saw only that sentence. The page now names
+ * the exact URL to add and the hand-pasted key as the way meanwhile. Other errors pass through as the provider wrote
+ * them.
+ *
+ * node test/restream-oauth-errors.test.js
+ */
+'use strict';
+const assert = require('assert');
+const { oauthErrorText } = require('../server/streaming/restream-routes');
+const { redirectUri } = require('../server/integrations/platform-oauth');
+
+const twitch = oauthErrorText('twitch', 'redirect_mismatch', 'The provided redirect_uri does not match');
+assert.ok(twitch.startsWith('Twitch refused the sign-in because this site\'s address is not on its app\'s list.'), twitch);
+assert.ok(twitch.includes(redirectUri('twitch')) && /\/api\/restream\/oauth\/twitch\/callback/.test(twitch), 'names the exact URL to add');
+assert.ok(/paste the stream key into the destination by hand/.test(twitch), 'and the way meanwhile');
+assert.ok(/YouTube refused the sign-in/.test(oauthErrorText('youtube', 'invalid_request', 'Invalid parameter value for redirect_uri: Mismatch')), 'a description about the redirect URI counts too');
+assert.strictEqual(oauthErrorText('kick', 'access_denied', 'The user denied the request'), 'The user denied the request', 'other errors pass through');
+assert.strictEqual(oauthErrorText('kick', 'access_denied', ''), 'access_denied');
+console.log('restream oauth errors: all checks passed');