fix(auth): request moderator OAuth scopes only on opt-in

Stop asking for mod scopes at normal login so existing core-only tokens
never hit a missing-permissions dialog. Gate moderated-channels Helix,
offer an Enable flow for mods/broadcasters, and upgrade atomically.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Tommy Chow 2026-08-09 15:33:06 -07:00
parent 2d65aeec29
commit cc772a7c90
15 changed files with 690 additions and 128 deletions

View file

@ -215,15 +215,16 @@ void main() {
});
group('validateToken', () {
test('returns granted scopes for valid token', () async {
test('returns scopes and user id for valid token', () async {
dioAdapter.onGet(
'https://id.twitch.tv/oauth2/validate',
(server) => server.reply(200, twitchValidateTokenResponse),
);
final scopes = await api.validateToken(token: 'valid_token');
final info = await api.validateToken(token: 'valid_token');
expect(scopes, ['chat:read', 'chat:edit']);
expect(info?.scopes, ['chat:read', 'chat:edit']);
expect(info?.userId, '12345');
});
test('returns null for expired token (401)', () async {
@ -232,9 +233,9 @@ void main() {
(server) => server.reply(401, {'message': 'invalid access token'}),
);
final scopes = await api.validateToken(token: 'expired_token');
final info = await api.validateToken(token: 'expired_token');
expect(scopes, isNull);
expect(info, isNull);
});
test('rethrows on server error', () {

View file

@ -0,0 +1,20 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:frosty/apis/unauthorized_interceptor.dart';
void main() {
group('moderation path helpers', () {
test('matches helix moderation paths', () {
expect(
isTwitchModeratedChannelsPath('/helix/moderation/channels'),
isTrue,
);
expect(isTwitchModeratorActionPath('/helix/moderation/chat'), isTrue);
expect(isTwitchModeratorActionPath('/helix/moderation/bans'), isTrue);
expect(
isTwitchModeratorActionPath('/helix/moderation/channels'),
isFalse,
);
expect(isTwitchModeratedChannelsPath('/helix/users'), isFalse);
});
});
}